feat(judger): uoj_judger_v2
All checks were successful
continuous-integration/drone/push Build is passing

ref: https://github-redirect.dependabot.com/UniversalOJ/UOJ-System/pull/113

Co-authored-by: vfleaking <vfleaking@163.com>
Co-authored-by: Yefori-Go <110314400+Yefori-Go@users.noreply.github.com>
This commit is contained in:
Baoshuo Ren 2022-10-04 20:57:49 +08:00
parent 354c417737
commit bcf5ce8b06
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
18 changed files with 7279 additions and 2206 deletions

View File

@ -1,9 +1,9 @@
FROM ubuntu:20.04
FROM ubuntu:22.04
ARG CLONE_ADDFLAG
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
RUN apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
ADD . /opt/uoj_judger
WORKDIR /opt/uoj_judger

View File

@ -3,7 +3,8 @@
getAptPackage(){
printf "\n\n==> Getting environment packages\n"
export DEBIAN_FRONTEND=noninteractive
apt-get update && apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.8 python3-requests
apt-get update
apt-get install -y vim ntp zip unzip curl wget build-essential fp-compiler python2.7 python3.10 python3-requests libseccomp-dev openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
}
setJudgeConf(){
@ -21,12 +22,6 @@ UOJEOF
su judger <<EOD
ln -s /var/uoj_data_copy /opt/uoj_judger/uoj_judger/data
cd /opt/uoj_judger && chmod +x judge_client
cat >uoj_judger/include/uoj_work_path.h <<UOJEOF
#define UOJ_WORK_PATH "/opt/uoj_judger/uoj_judger"
#define UOJ_JUDGER_BASESYSTEM_UBUNTU1804
#define UOJ_JUDGER_PYTHON3_VERSION "3.8"
#define UOJ_JUDGER_FPC_VERSION "3.0.4"
UOJEOF
cd uoj_judger && make -j$(($(nproc) + 1))
EOD
}

View File

@ -5,28 +5,21 @@ import sys
import os
import pipes
import socket
from threading import Thread
from threading import Thread, Lock, Condition, RLock
import fcntl
import shutil
import traceback
import time
import logging
import codecs
from contextlib import closing
from typing import *
import requests
import queue as queue
from queue import Queue, Empty
taskQ = Queue()
submission = None
jconf: dict
# path related function
def uoj_url(uri):
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
def uoj_judger_path(path = ''):
return "uoj_judger" + path
# os related funciton
def clean_up_folder(path):
@ -37,14 +30,25 @@ def clean_up_folder(path):
else:
shutil.rmtree(f_path)
def execute(cmd):
if os.system(cmd):
if os.system(cmd) != 0:
raise Exception('failed to execute: %s' % cmd)
def freopen(f, g):
os.dup2(g.fileno(), f.fileno())
g.close()
# utf 8
def uoj_text_replace(e):
return ''.join('<b>\\x%02x</b>' % b for b in e.object[e.start:e.end]), e.end
codecs.register_error('uoj_text_replace', uoj_text_replace)
# init
def init():
global jconf
@ -57,57 +61,134 @@ def init():
assert 'judger_password' in jconf
assert 'socket_port' in jconf
assert 'socket_password' in jconf
if 'judger_cpus' not in jconf:
jconf['judger_cpus'] = [
'0'] # it should be distinct physical cores! Usually, you can just set it to some even numbers
# socket server
def socket_server_loop():
SOCK_CLOEXEC = 524288
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', jconf['socket_port']))
s.listen(5)
while True:
try:
conn, addr = s.accept()
with closing(conn) as conn:
data = conn.recv(1024).decode()
assert data != None
task = json.loads(data)
assert task['password'] == jconf['socket_password']
assert 'cmd' in task
# path related function
def uoj_url(uri):
return ("%s://%s%s" % (jconf['uoj_protocol'], jconf['uoj_host'], uri)).rstrip('/')
taskQ.put(task)
if task['cmd'] == 'stop':
print('the judge client is closing...')
taskQ.join()
conn.sendall(b'ok')
return 'stop'
except Exception:
print('['+time.asctime()+']', 'connection rejected', file=sys.stderr)
traceback.print_exc()
def uoj_judger_path(path=''):
return os.getcwd() + "/uoj_judger" + path
class RWLock:
def __init__(self):
self.rwlock = 0
self.writers_waiting = 0
self.monitor = Lock()
self.readers_ok = Condition(self.monitor)
self.writers_ok = Condition(self.monitor)
def acquire_read(self):
self.monitor.acquire()
while self.rwlock < 0 or self.writers_waiting:
self.readers_ok.wait()
self.rwlock += 1
self.monitor.release()
def acquire_write(self):
self.monitor.acquire()
while self.rwlock != 0:
self.writers_waiting += 1
self.writers_ok.wait()
self.writers_waiting -= 1
self.rwlock = -1
self.monitor.release()
def promote(self):
self.monitor.acquire()
self.rwlock -= 1
while self.rwlock != 0:
self.writers_waiting += 1
self.writers_ok.wait()
self.writers_waiting -= 1
self.rwlock = -1
self.monitor.release()
def demote(self):
self.monitor.acquire()
self.rwlock = 1
self.readers_ok.notifyAll()
self.monitor.release()
def release(self):
self.monitor.acquire()
if self.rwlock < 0:
self.rwlock = 0
else:
print('['+time.asctime()+']', 'a new task accomplished', file=sys.stderr)
self.rwlock -= 1
wake_writers = self.writers_waiting and self.rwlock == 0
wake_readers = self.writers_waiting == 0
self.monitor.release()
if wake_writers:
self.writers_ok.acquire()
self.writers_ok.notify()
self.writers_ok.release()
elif wake_readers:
self.readers_ok.acquire()
self.readers_ok.notify_all()
self.readers_ok.release()
def start_judger_server():
global socket_server_thread
print_judge_client_status()
print('hello!', file=sys.stderr)
class Judger:
problem_data_lock = RWLock()
send_and_fetch_lock = RLock()
socket_server_thread = Thread(target = socket_server_loop)
socket_server_thread.setDaemon(True)
socket_server_thread.start()
judger_id: int
cpus: str
main_path: str
submission: Optional[dict]
submission_judged: bool
main_thread: Optional[Thread]
report_thread: Optional[Thread]
_taskQ: Queue
judger_loop()
def log_judge_client_status(self):
logging.info('submission: ' + str(self.submission))
# report thread
def report_loop():
if 'is_hack' in submission:
def __init__(self, judger_id, cpus):
self.judger_id = judger_id
self.cpus = cpus
self.main_path = '/tmp/' + jconf['judger_name'] + '/' + str(self.judger_id)
self.submission = None
self.main_thread = Thread(target=self._main_loop)
self.report_thread = None
self.submission_judged = False
self._taskQ = Queue()
def start(self):
execute('mkdir -p %s' % (self.main_path + '/result'))
execute('mkdir -p %s' % (self.main_path + '/work'))
self.log_judge_client_status()
logging.info('hello from judger #%d' % self.judger_id)
self.main_thread.start()
def suspend(self):
self._taskQ.put('suspend')
self._taskQ.join()
def resume(self):
self._taskQ.put('resume')
self._taskQ.join()
def exit(self):
self._taskQ.put('exit')
self._taskQ.join()
self.main_thread.join()
# report thread
def _report_loop(self):
if 'is_hack' in self.submission:
return
while not submission_judged:
while not self.submission_judged:
try:
with open(uoj_judger_path('/result/cur_status.txt'), 'r') as f:
with open(self.main_path + '/result/cur_status.txt', 'r') as f:
fcntl.flock(f, fcntl.LOCK_SH)
try:
status = f.read(100)
@ -119,8 +200,8 @@ def report_loop():
if status != None:
data = {}
data['update-status'] = True
data['id'] = submission['id']
if 'is_custom_test' in submission:
data['id'] = self.submission['id']
if 'is_custom_test' in self.submission:
data['is_custom_test'] = True
data['status'] = status
uoj_interact(data)
@ -128,48 +209,9 @@ def report_loop():
except Exception:
pass
# handle task in main thread
def handle_task():
need_restart = False
try:
while True:
task = taskQ.get_nowait()
if task['cmd'] == 'update':
try:
uoj_download('/judger', 'judger_update.zip')
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
except:
print("error when update", file=sys.stderr)
if jconf['judger_name'] == 'main_judger':
uoj_sync_judge_client()
need_restart = True
elif task['cmd'] == 'stop':
taskQ.task_done()
socket_server_thread.join()
print_judge_client_status()
print("goodbye!", file=sys.stderr)
sys.exit(0)
taskQ.task_done()
except Empty:
pass
if need_restart:
os.execl('./judge_client', './judge_client')
def print_judge_client_status():
print('[' + time.asctime() + ']', end=' ', file=sys.stderr)
if submission != None:
print(submission, end=' ', file=sys.stderr)
print(file=sys.stderr)
# interact with uoj_judger
def get_judger_result():
def _get_result(self):
res = {}
with open(uoj_judger_path('/result/result.txt'), 'r') as fres:
with open(self.main_path + '/result/result.txt', 'r', encoding='utf-8', errors='uoj_text_replace') as fres:
res['score'] = 0
res['time'] = 0
res['memory'] = 0
@ -195,80 +237,291 @@ def get_judger_result():
res['status'] = 'Judged'
return res
def update_problem_data(problem_id, problem_mtime):
def _remove_problem_data(self, problem_id):
quoted_path = pipes.quote(uoj_judger_path(f'/data/{problem_id}'))
execute(f'chmod 700 {quoted_path} -R && rm -rf {quoted_path}')
def _update_problem_data_atime(self, problem_id):
path = uoj_judger_path(f'/data/{problem_id}')
execute(f'touch -a {pipes.quote(path)}')
def _update_problem_data(self, problem_id, problem_mtime):
Judger.problem_data_lock.acquire_read()
try:
if jconf['judger_name'] == 'main_judger':
return
copy_name = uoj_judger_path('/data/%d' % problem_id)
copy_zip_name = uoj_judger_path('/data/%d.zip' % problem_id)
copy_name = uoj_judger_path(f'/data/{problem_id}')
copy_zip_name = uoj_judger_path(f'/data/{problem_id}.zip')
if os.path.isdir(copy_name):
quoted_copy_name = pipes.quote(copy_name)
if os.path.getmtime(copy_name) >= problem_mtime:
execute('touch -a %s' % quoted_copy_name)
self._update_problem_data_atime(problem_id)
return
else:
execute('chmod 700 %s -R && rm -rf %s' % (quoted_copy_name, quoted_copy_name))
del_list = sorted(os.listdir(uoj_judger_path('/data')), key=lambda fname: os.path.getatime(uoj_judger_path('/data/%s' % fname)))[:-99]
for fname in del_list:
quoted_fname = pipes.quote(uoj_judger_path('/data/%s' % fname))
os.system('chmod 700 %s -R && rm -rf %s' % (quoted_fname, quoted_fname))
uoj_download('/problem/%d' % problem_id, copy_zip_name)
execute('cd %s && unzip -o -q %d.zip && rm %d.zip && chmod -w %d -R' % (uoj_judger_path('/data'), problem_id, problem_id, problem_id))
except Exception:
print_judge_client_status()
traceback.print_exc()
raise Exception('failed to update problem data of #%d' % problem_id)
Judger.problem_data_lock.promote()
self._remove_problem_data(problem_id)
else:
print_judge_client_status()
print('updated problem data of #%d successfully' % problem_id, file=sys.stderr)
Judger.problem_data_lock.promote()
def judge():
global report_thread
global submission_judged
del_list = sorted(os.listdir(uoj_judger_path('/data')),
key=lambda p: os.path.getatime(uoj_judger_path(f'/data/{p}')))[:-99]
for p in del_list:
self._remove_problem_data(p)
clean_up_folder(uoj_judger_path('/work'))
clean_up_folder(uoj_judger_path('/result'))
update_problem_data(submission['problem_id'], submission['problem_mtime'])
uoj_download(f'/problem/{problem_id}', copy_zip_name)
execute('cd %s && unzip -q %d.zip && rm %d.zip && chmod -w %d -R' % (
uoj_judger_path('/data'), problem_id, problem_id, problem_id))
os.utime(uoj_judger_path(f'/data/{problem_id}'), (time.time(), problem_mtime))
except Exception:
self.log_judge_client_status()
logging.exception('problem update error')
raise Exception(f'failed to update problem data of #{problem_id}')
else:
self.log_judge_client_status()
logging.info(f'updated problem data of #{problem_id} successfully')
finally:
Judger.problem_data_lock.release()
with open(uoj_judger_path('/work/submission.conf'), 'w') as fconf:
uoj_download(submission['content']['file_name'], uoj_judger_path('/work/all.zip'))
execute("cd %s && unzip -o -q all.zip && rm all.zip" % pipes.quote(uoj_judger_path('/work')))
for k, v in submission['content']['config']:
def _judge(self):
clean_up_folder(self.main_path + '/work')
clean_up_folder(self.main_path + '/result')
self._update_problem_data(self.submission['problem_id'], self.submission['problem_mtime'])
with open(self.main_path + '/work/submission.conf', 'w') as fconf:
uoj_download(self.submission['content']['file_name'], self.main_path + '/work/all.zip')
execute("cd %s && unzip -q all.zip && rm all.zip" % pipes.quote(self.main_path + '/work'))
for k, v in self.submission['content']['config']:
print(k, v, file=fconf)
if 'is_hack' in submission:
if submission['hack']['input_type'] == 'USE_FORMATTER':
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input_raw.txt'))
if 'is_hack' in self.submission:
if self.submission['hack']['input_type'] == 'USE_FORMATTER':
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input_raw.txt')
execute('%s <%s >%s' % (
pipes.quote(uoj_judger_path('/run/formatter')),
pipes.quote(uoj_judger_path('/work/hack_input_raw.txt')),
pipes.quote(uoj_judger_path('/work/hack_input.txt'))))
pipes.quote(self.main_path + '/work/hack_input_raw.txt'),
pipes.quote(self.main_path + '/work/hack_input.txt')
))
else:
uoj_download(submission['hack']['input'], uoj_judger_path('/work/hack_input.txt'))
uoj_download(self.submission['hack']['input'], self.main_path + '/work/hack_input.txt')
print('test_new_hack_only on', file=fconf)
elif 'is_custom_test' in submission:
elif 'is_custom_test' in self.submission:
print('custom_test on', file=fconf)
report_thread = Thread(target = report_loop)
report_thread.setDaemon(True)
self.submission_judged = False
self.report_thread = Thread(target=self._report_loop)
self.report_thread.start()
submission_judged = False
report_thread.start()
execute(pipes.quote(uoj_judger_path('/main_judger')))
submission_judged = True
report_thread.join()
Judger.problem_data_lock.acquire_read()
try:
main_judger_cmd = pipes.quote(uoj_judger_path('/main_judger'))
if self.cpus != 'all':
main_judger_cmd = 'taskset -c %s %s' % (pipes.quote(self.cpus), main_judger_cmd)
execute('cd %s && %s' % (pipes.quote(self.main_path), main_judger_cmd))
finally:
Judger.problem_data_lock.release()
self.submission_judged = True
self.report_thread.join()
self.report_thread = None
return self._get_result()
def _send_and_fetch(self, result=None, fetch_new=True):
"""send judgement result, and fetch new submission to judge"""
data = {}
files = {}
if not fetch_new:
data['fetch_new'] = False
if result is not None:
data['submit'] = True
if 'is_hack' in self.submission:
data['is_hack'] = True
data['id'] = self.submission['hack']['id']
if result != False and result['score']:
try:
logging.info("succ hack!")
files = {
('hack_input', open(self.main_path + '/work/hack_input.txt', 'rb')),
('std_output', open(self.main_path + '/work/std_output.txt', 'rb'))
}
except Exception:
self.log_judge_client_status()
logging.exception('hack: submit error')
result = False
elif 'is_custom_test' in self.submission:
data['is_custom_test'] = True
data['id'] = self.submission['id']
else:
data['id'] = self.submission['id']
if 'judge_time' in self.submission:
data['judge_time'] = self.submission['judge_time']
if 'tid' in self.submission:
data['tid'] = self.submission['tid']
if result == False:
result = {
'score': 0,
'error': 'Judgment Failed',
'details': 'Unknown Error'
}
result['status'] = 'Judged'
data['result'] = json.dumps(result, ensure_ascii=False)
while True:
Judger.send_and_fetch_lock.acquire()
try:
ret = uoj_interact(data, files)
except Exception:
self.log_judge_client_status()
logging.exception('uoj_interact error')
else:
break
finally:
Judger.send_and_fetch_lock.release()
time.sleep(2)
try:
self.submission = json.loads(ret)
except Exception:
if ret != 'Nothing to judge':
logging.info(ret)
self.submission = None
return False
else:
return True
def _main_loop(self):
while True:
if not self._taskQ.empty():
while True:
task = self._taskQ.get()
if task == 'resume':
self._taskQ.task_done()
break
elif task == 'exit':
self._taskQ.task_done()
return
else:
self._taskQ.task_done()
if not self._send_and_fetch():
time.sleep(2)
continue
self.log_judge_client_status()
logging.info('judging')
while True:
try:
res = self._judge()
except Exception:
self.log_judge_client_status()
logging.exception('judge error')
res = False
if not self._send_and_fetch(result=res, fetch_new=self._taskQ.empty()):
break
class JudgerServer:
taskQ: Queue
judgers: List[Judger]
socket_thread: Thread
def __init__(self):
self.taskQ = Queue()
self.judgers = [Judger(i, cpus) for i, cpus in enumerate(jconf['judger_cpus'])]
self.socket_thread = Thread(target=self._socket_loop)
def _socket_loop(self):
try:
SOCK_CLOEXEC = 524288
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM | SOCK_CLOEXEC)) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', jconf['socket_port']))
s.listen(5)
while True:
try:
conn, addr = s.accept()
with closing(conn) as conn:
data = conn.recv(1024)
assert data != None
task = json.loads(data)
assert task['password'] == jconf['socket_password']
assert 'cmd' in task
self.taskQ.put(task)
if task['cmd'] == 'stop':
logging.info('the judge client is closing...')
self.taskQ.join()
conn.sendall(b'ok')
return 'stop'
except Exception:
logging.exception('connection rejected')
else:
logging.info('a new task accomplished')
except Exception:
self.taskQ.put({'cmd': 'stop'})
logging.exception('socket server error!')
def run(self):
self.socket_thread.start()
for judger in self.judgers:
judger.start()
while True:
task = self.taskQ.get()
need_restart = False
block_wait = False
for judger in self.judgers:
judger.suspend()
try:
while True:
if task['cmd'] in ['update', 'self-update']:
try:
uoj_download('/judger', 'judger_update.zip')
execute('unzip -o judger_update.zip && cd %s && make clean && make' % uoj_judger_path())
except:
print(sys.stderr, "error when update")
if jconf['judger_name'] == 'main_judger':
uoj_sync_judge_client()
need_restart = True
elif task['cmd'] == 'stop':
self.taskQ.task_done()
self.socket_thread.join()
for judger in self.judgers:
judger.exit()
logging.info("goodbye!")
sys.exit(0)
self.taskQ.task_done()
task = self.taskQ.get(block=block_wait)
except Empty:
pass
if need_restart:
os.execl('./judge_client', './judge_client')
for judger in self.judgers:
judger.resume()
return get_judger_result()
# interact with uoj web server
def uoj_interact(data, files = {}):
# TODO: set a larger timeout
def uoj_interact(data, files={}):
data = data.copy()
data.update({
'judger_name': jconf['judger_name'],
'password': jconf['judger_password']
})
return requests.post(uoj_url('/judge/submit'), data=data, files=files).text
def uoj_download(uri, filename):
data = {
'judger_name': jconf['judger_name'],
@ -279,6 +532,8 @@ def uoj_download(uri, filename):
for chunk in r.iter_content(chunk_size=65536):
if chunk:
f.write(chunk)
def uoj_sync_judge_client():
data = {
'judger_name': jconf['judger_name'],
@ -288,108 +543,16 @@ def uoj_sync_judge_client():
if ret != "ok":
raise Exception('failed to sync judge clients: %s' % ret)
def send_and_fetch(result = None, fetch_new = True):
global submission
"""send judgement result, and fetch new submission to judge"""
data = {}
files = {}
if not fetch_new:
data['fetch_new'] = False
if result != None:
data['submit'] = True
if 'is_hack' in submission:
data['is_hack'] = True
data['id'] = submission['hack']['id']
if result != False and result['score']:
try:
print("succ hack!", file=sys.stderr)
files = {
('hack_input', open('uoj_judger/work/hack_input.txt', 'r')),
('std_output', open('uoj_judger/work/std_output.txt', 'r'))
}
except Exception:
print_judge_client_status()
traceback.print_exc()
result = False
elif 'is_custom_test' in submission:
data['is_custom_test'] = True
data['id'] = submission['id']
else:
data['id'] = submission['id']
if result == False:
result = {
'score': 0,
'error': 'Judgement Failed',
'details': 'Unknown Error'
}
result['status'] = 'Judged'
data['result'] = json.dumps(result, ensure_ascii=False)
while True:
try:
ret = uoj_interact(data, files)
print(ret)
except Exception:
print_judge_client_status()
traceback.print_exc()
else:
break
time.sleep(2)
try:
submission = json.loads(ret)
except Exception as e:
submission = None
return False
else:
return True
# judge client
def judger_loop():
ok = False
while True:
fetch_new = True
if ok and not (taskQ.empty() and socket_server_thread.isAlive()):
fetch_new = False
if not ok:
while True:
if not taskQ.empty():
handle_task()
if not socket_server_thread.isAlive():
raise Exception('socket server exited unexpectedly')
if send_and_fetch():
break
print('['+time.asctime()+']', 'Nothing to judge...')
time.sleep(2)
ok = True
print_judge_client_status()
print('judging', file=sys.stderr)
try:
res = judge()
except Exception:
print_judge_client_status()
traceback.print_exc()
res = False
ok = send_and_fetch(result=res,fetch_new=fetch_new)
# main function
def main():
init()
logging.basicConfig(filename='log/judge.log', level=logging.INFO,
format='%(asctime)-15s [%(levelname)s]: %(message)s')
if len(sys.argv) == 1:
start_judger_server()
JudgerServer().run()
if len(sys.argv) == 2:
if sys.argv[1] == 'start':
pid = os.fork()
@ -398,20 +561,24 @@ def main():
elif pid > 0:
return
else:
freopen(sys.stdout, open(os.devnull, 'wb'))
freopen(sys.stderr, open('log/judge.log', 'ab', buffering=0))
start_judger_server()
elif sys.argv[1] == 'update':
freopen(sys.stdout, open(os.devnull, 'w'))
freopen(sys.stderr, open('log/judge.log', 'a', encoding='utf-8', errors='uoj_text_replace'))
JudgerServer().run()
elif sys.argv[1] in ['update', 'self-update']:
try:
try:
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
s.connect(('127.0.0.1', jconf['socket_port']))
s.sendall(json.dumps({
'password': jconf['socket_password'],
'cmd': 'update'
'cmd': sys.argv[1]
}).encode())
return
except OSError:
JudgerServer.update(broadcast=sys.argv[1] == 'update')
return
except Exception:
traceback.print_exc()
logging.exception('update error')
raise Exception('update failed')
elif sys.argv[1] == 'stop':
try:
@ -421,17 +588,17 @@ def main():
'password': jconf['socket_password'],
'cmd': 'stop'
}).encode())
if s.recv(10).decode() != 'ok':
if s.recv(10) != b'ok':
raise Exception('stop failed')
return
except Exception:
traceback.print_exc()
logging.exception('stop error')
raise Exception('stop failed')
raise Exception('invalid argument')
try:
main()
except Exception:
print_judge_client_status()
traceback.print_exc()
logging.exception('critical error!')
sys.exit(1)

View File

@ -1,5 +1,5 @@
INCLUDE_PATH = include
CXXFLAGS = -I./include -O2
CXXFLAGS = -I./include -O2 --std=c++17 -Wall -lstdc++fs
EXE_CHECKER = \
builtin/checker/bcmp \
@ -26,24 +26,35 @@ EXE = main_judger \
run/formatter \
run/run_program \
run/run_interaction \
builtin/judger/judger
run/compile \
builtin/judger/judger \
$(EXE_CHECKER)
all: $(EXE) $(EXE_CHECKER)
runner: $(EXE)
checker: $(EXE_CHECKER)
OBJ = tests/catch_amalgamated.o tests/test.o
all: $(EXE)
% : %.cpp
$(CXX) $(CXXFLAGS) $(EXTRA_CXXFLAGS) $< -o $@
run/run_program: include/uoj_env.h run/run_program_conf.h
run/formatter : include/testlib.h
run/run_interaction: run/run_interaction.cpp include/uoj_env.h
$(CXX) $(CXXFLAGS) --std=c++11 -pthread $< -o $@
run/compile : include/uoj_run.h
builtin/judger/judger: include
main_judger: include
run/run_program : run/run_program.cpp run/run_program_conf.h run/run_program_sandbox.h include/uoj_run.h
$(CXX) $(CXXFLAGS) $< -o $@ -lseccomp -pthread
run/run_interaction : run/run_interaction.cpp include/uoj_run.h
$(CXX) $(CXXFLAGS) $< -o $@ -pthread
builtin/judger/judger: include/*.h
main_judger: include/*.h
tests/test.o: include/*.h
tests/test: tests/catch_amalgamated.o tests/test.o
$(CXX) $(CXXFLAGS) $^ -o $@ -pthread
$(EXE_CHECKER): include/testlib.h
clean:
rm -f $(EXE) $(EXE_CHECKER)
rm -f $(EXE) $(OBJ)

View File

@ -1,150 +1,51 @@
#include "uoj_judger.h"
struct SubtaskInfo {
bool passed;
int score;
PointInfo submit_answer_test_point_with_auto_generation(int i, TestPointConfig tpc = TestPointConfig()) {
static set<string> compiled;
static set<int> generated;
SubtaskInfo() {
int output_file_id = conf_int("output_file_id", i, i);
tpc.submit_answer = true;
tpc.output_file_name = work_path + "/" + conf_output_file_name(conf_int("output_file_id", i, i));
string gen_name = conf_str("output_gen", output_file_id, "");
if (gen_name.empty()) {
generated.insert(output_file_id);
}
if (generated.count(output_file_id)) {
return test_point("", i, tpc);
}
SubtaskInfo(const bool &_p, const int &_s)
: passed(_p), score(_s){}
};
void ordinary_test() {
int n = conf_int("n_tests", 10);
int m = conf_int("n_ex_tests", 0);
int nT = conf_int("n_subtasks", 0);
tpc.limit = conf_run_limit("gen", output_file_id, RL_GENERATOR_DEFAULT);
generated.insert(output_file_id);
if (!conf_is("submit_answer", "on")) {
report_judge_status_f("Compiling");
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
if (!compiled.count(gen_name)) {
report_judge_status_f("Compiling %s", gen_name.c_str());
if (auto c_ret = compile_submission_program(gen_name); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
compiled.insert(gen_name);
}
bool passed = true;
if (nT == 0) {
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Test #%d", i);
PointInfo po = test_point("answer", i);
if (po.scr != 100) {
passed = false;
}
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
add_point_info(po);
}
} else if (nT == 1 && conf_str("subtask_type", 1, "packed") == "packed") {
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Test #%d", i);
PointInfo po = test_point("answer", i);
if (po.scr != 100) {
passed = false;
po.scr = i == 1 ? 0 : -100;
add_point_info(po);
break;
tpc.submit_answer = false;
return test_point(gen_name, i, tpc);
}
void ordinary_test() {
if (conf_is("submit_answer", "on")) {
PrimaryDataTestConfig pdtc;
pdtc.disable_ex_tests = true;
primary_data_test([](int i) {
return submit_answer_test_point_with_auto_generation(i);
}, pdtc);
} else {
po.scr = i == 1 ? 100 : 0;
add_point_info(po);
report_judge_status_f("Compiling");
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
}
} else {
map<int, SubtaskInfo> subtasks;
map<int,int> minScore;
for (int t = 1; t <= nT; t++) {
string subtaskType = conf_str("subtask_type", t, "packed");
int startI = conf_int("subtask_end", t - 1, 0) + 1;
int endI = conf_int("subtask_end", t, 0);
vector<PointInfo> points;
minScore[t] = 100;
vector<int> dependences;
if (conf_str("subtask_dependence", t, "none") == "many") {
string cur = "subtask_dependence_" + vtos(t);
int p = 1;
while (conf_int(cur, p, 0) != 0) {
dependences.push_back(conf_int(cur, p, 0));
p++;
}
} else if (conf_int("subtask_dependence", t, 0) != 0) {
dependences.push_back(conf_int("subtask_dependence", t, 0));
}
bool skipped = false;
for (vector<int>::iterator it = dependences.begin(); it != dependences.end(); it++) {
if (subtaskType == "packed") {
if (!subtasks[*it].passed) {
skipped = true;
break;
}
} else if (subtaskType == "min") {
minScore[t] = min(minScore[t], minScore[*it]);
}
}
if (skipped) {
add_subtask_info(t, 0, "Skipped", points);
continue;
}
int tfull = conf_int("subtask_score", t, 100 / nT);
int tscore = scale_score(minScore[t], tfull);
string info = "Accepted";
for (int i = startI; i <= endI; i++) {
report_judge_status_f("Judging Test #%d of Subtask #%d", i, t);
PointInfo po = test_point("answer", i);
if (subtaskType == "packed") {
if (po.scr != 100) {
passed = false;
po.scr = i == startI ? 0 : -tfull;
tscore = 0;
points.push_back(po);
info = po.info;
break;
} else {
po.scr = i == startI ? tfull : 0;
tscore = tfull;
points.push_back(po);
}
} else if (subtaskType == "min") {
minScore[t] = min(minScore[t], po.scr);
if (po.scr != 100) {
passed = false;
}
po.scr = scale_score(po.scr, tfull);
if (po.scr <= tscore) {
tscore = po.scr;
points.push_back(po);
info = po.info;
} else {
points.push_back(po);
}
}
}
subtasks[t] = SubtaskInfo(info == "Accepted", tscore);
add_subtask_info(t, tscore, info, points);
}
}
if (conf_is("submit_answer", "on") || !passed) {
end_judge_ok();
}
tot_score = 100;
for (int i = 1; i <= m; i++) {
report_judge_status_f("Judging Extra Test #%d", i);
PointInfo po = test_point("answer", -i);
if (po.scr != 100) {
po.num = -1;
po.info = "Extra Test Failed : " + po.info + " on " + vtos(i);
po.scr = -3;
add_point_info(po);
end_judge_ok();
}
}
if (m != 0) {
PointInfo po(-1, 0, -1, -1, "Extra Test Passed", "", "", "");
add_point_info(po);
primary_data_test([](int i) {
return test_point("answer", i);
});
}
end_judge_ok();
}
@ -153,8 +54,7 @@ void hack_test() {
if (conf_is("submit_answer", "on")) {
end_judge_judgement_failed("Hack is not supported in this problem.");
} else {
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
TestPointConfig tpc;
@ -170,80 +70,52 @@ void hack_test() {
void sample_test() {
if (conf_is("submit_answer", "on")) {
int n = conf_int("n_tests", 10);
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Test #%d", i);
if (conf_is("check_existence_only_in_sample_test", "on")) {
main_data_test([](int i) {
TestPointConfig tpc = TestPointConfig();
tpc.auto_complete(i);
string usrout = file_preview(tpc.output_file_name);
if (usrout == "") {
add_point_info(PointInfo(i, 0, -1, -1,
"default",
file_preview(tpc.input_file_name), usrout,
"wrong answer empty file\n"));
tpc.checker = "nonempty";
return submit_answer_test_point_with_auto_generation(i, tpc);
});
} else {
PointInfo po = PointInfo(i, 100, -1, -1,
"default",
file_preview(tpc.input_file_name), usrout,
"ok nonempty file\n");
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
add_point_info(po);
}
} else {
PointInfo po = test_point("answer", i);
main_data_test([](int i) {
PointInfo po = submit_answer_test_point_with_auto_generation(i);
if (po.scr != 0) {
po.info = "Accepted";
po.scr = 100;
}
po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n));
po.res = "no comment";
add_point_info(po);
}
return po;
});
}
end_judge_ok();
} else {
report_judge_status_f("Compiling");
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
int n = conf_int("n_sample_tests", 0);
bool passed = true;
for (int i = 1; i <= n; i++) {
report_judge_status_f("Judging Sample Test #%d", i);
PointInfo po = test_point("answer", -i);
po.num = i;
if (po.scr != 100) {
passed = false;
}
po.scr = scale_score(po.scr, 100 / n);
add_point_info(po);
}
if (passed) {
tot_score = 100;
}
sample_data_test([](int i) {
return test_point("answer", i);
});
end_judge_ok();
}
}
void custom_test() {
if (conf_is("submit_answer", "on")) {
end_judge_judgement_failed("Custom test is not supported in this problem.");
} else {
report_judge_status_f("Compiling");
RunCompilerResult c_ret = !conf_is("with_implementer", "on") ? compile("answer") : compile_with_implementer("answer");
if (!c_ret.succeeded) {
if (auto c_ret = compile_submission_program("answer"); !c_ret.succeeded) {
end_judge_compile_error(c_ret);
}
CustomTestConfig ctc;
if (conf_is("submit_answer", "on")) {
ctc.base_limit = conf_run_limit("gen", 0, RL_GENERATOR_DEFAULT);
}
report_judge_status_f("Judging");
add_custom_test_info(ordinary_custom_test("answer"));
add_custom_test_info(ordinary_custom_test("answer", ctc));
end_judge_ok();
}
}
int main(int argc, char **argv) {

View File

@ -1,38 +0,0 @@
#include "uoj_work_path.h"
#define UOJ_DATA_PATH UOJ_WORK_PATH "/data"
#define UOJ_RESULT_PATH UOJ_WORK_PATH "/result"
#define RS_SPJ_BASE 1000
#define failed_spj RS_SPJ_BASE
#define successed_hack 100000
#define RS_SPJ RS_SPJ_BASE
#define RS_HACK successed_hack
#define RS_AC 0
#define RS_WA 1
#define RS_RE 2
#define RS_MLE 3
#define RS_TLE 4
#define RS_OLE 5
#define RS_DGS 6
#define RS_JGF 7
#define RS_SPJ_AC (RS_SPJ + RS_AC)
#define RS_SPJ_RE (RS_SPJ_BASE + RS_RE)
#define RS_SPJ_MLE (RS_SPJ_BASE + RS_MLE)
#define RS_SPJ_TLE (RS_SPJ_BASE + RS_TLE)
#define RS_SPJ_OLE (RS_SPJ_BASE + RS_OLE)
#define RS_SPJ_DGS (RS_SPJ_BASE + RS_DGS)
#define RS_SPJ_JGF (RS_SPJ_BASE + RS_JGF)
#define RS_HK_RE (successed_hack + RS_RE)
#define RS_HK_MLE (successed_hack + RS_MLE)
#define RS_HK_TLE (successed_hack + RS_TLE)
#define RS_HK_OLE (successed_hack + RS_OLE)
#define RS_HK_DGS (successed_hack + RS_DGS)
#define RS_HK_JGF (successed_hack + RS_JGF)
#define RS_HK_SPJ_RE (successed_hack + RS_SPJ_RE)
#define RS_HK_SPJ_MLE (successed_hack + RS_SPJ_MLE)
#define RS_HK_SPJ_TLE (successed_hack + RS_SPJ_TLE)
#define RS_HK_SPJ_OLE (successed_hack + RS_SPJ_OLE)
#define RS_HK_SPJ_DGS (successed_hack + RS_SPJ_DGS)
#define RS_HK_SPJ_JGF (successed_hack + RS_SPJ_JGF)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,438 @@
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <fstream>
#include <cstdarg>
#include <filesystem>
#include <exception>
#include <stdexcept>
#define UOJ_GCC "/usr/bin/gcc-11"
#define UOJ_GPLUSPLUS "/usr/bin/g++-11"
#define UOJ_PYTHON2_7 "/usr/bin/python2.7"
#define UOJ_PYTHON3 "/usr/bin/python3.10"
#define UOJ_FPC "/usr/bin/fpc"
#define UOJ_OPEN_JDK8 "/usr/lib/jvm/java-8-openjdk-amd64"
#define UOJ_OPEN_JDK11 "/usr/lib/jvm/java-11-openjdk-amd64"
#define UOJ_OPEN_JDK17 "/usr/lib/jvm/java-17-openjdk-amd64"
std::string escapeshellarg(int arg) {
return std::to_string(arg);
}
std::string escapeshellarg(const std::string &arg) {
std::string res = "'";
for (char c : arg) {
if (c == '\'') {
res += "'\\''";
} else {
res += c;
}
}
res += "'";
return res;
}
template <typename T>
std::ostream& spaced_out(std::ostream &out, const T &arg) {
return out << arg;
}
template <typename T, typename... Args>
std::ostream& spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
return spaced_out(out << arg << " ", rest...);
}
template <typename T>
std::ostream& add_spaced_out(std::ostream &out, const T &arg) {
return out << " " << arg;
}
template <typename T, typename... Args>
std::ostream& add_spaced_out(std::ostream &out, const T &arg, const Args& ...rest) {
return spaced_out(out << " " << arg, rest...);
}
template <typename... Args>
int execute(const Args& ...args) {
std::ostringstream sout;
spaced_out(sout, args...);
#ifdef UOJ_SHOW_EVERY_CMD
std::cerr << sout.str() << std::endl;
#endif
int status = system(sout.str().c_str());
if (status == -1 || !WIFEXITED(status)) {
return -1;
}
return WEXITSTATUS(status);
}
int executef(const char *fmt, ...) {
const int L = 1 << 10;
char cmd[L];
va_list ap;
va_start(ap, fmt);
int res = vsnprintf(cmd, L, fmt, ap);
if (res < 0 || res >= L) {
return -1;
}
res = execute(cmd);
va_end(ap);
return res;
}
class cannot_determine_class_name_error : std::invalid_argument {
public:
explicit cannot_determine_class_name_error()
: std::invalid_argument("cannot determine the class name!") {}
};
std::string get_class_name_from_file(const std::string &fname) {
std::ifstream fin(fname);
if (!fin) {
throw cannot_determine_class_name_error();
}
std::string class_name;
if (!(fin >> class_name)) {
throw cannot_determine_class_name_error();
}
if (class_name.length() > 100) {
throw cannot_determine_class_name_error();
}
for (char &c : class_name) {
if (!isalnum(c) && c != '_') {
throw cannot_determine_class_name_error();
}
}
return class_name;
}
bool put_class_name_to_file(const std::string &fname, const std::string &class_name) {
std::ofstream fout(fname);
if (!fout) {
return false;
}
if (!(fout << class_name << std::endl)) {
return false;
}
return true;
}
std::map<std::string, std::string> lang_upgrade_map = {
{"Java7" , "Java8" },
{"Java14", "Java17" },
};
std::string upgraded_lang(const std::string &lang) {
return lang_upgrade_map.count(lang) ? lang_upgrade_map[lang] : lang;
}
namespace runp {
namespace fs = std::filesystem;
fs::path run_path;
struct limits_t {
int time;
int memory;
int output;
int real_time;
int stack;
limits_t() = default;
limits_t(const int &_time, const int &_memory, const int &_output)
: time(_time), memory(_memory), output(_output), real_time(-1), stack(-1) {
}
};
// result type
enum RS_TYPE {
RS_AC = 0,
RS_WA = 1,
RS_RE = 2,
RS_MLE = 3,
RS_TLE = 4,
RS_OLE = 5,
RS_DGS = 6,
RS_JGF = 7
};
inline std::string rstype_str(RS_TYPE id) {
switch (id) {
case RS_AC: return "Accepted";
case RS_WA: return "Wrong Answer";
case RS_RE : return "Runtime Error";
case RS_MLE: return "Memory Limit Exceeded";
case RS_TLE: return "Time Limit Exceeded";
case RS_OLE: return "Output Limit Exceeded";
case RS_DGS: return "Dangerous Syscalls";
case RS_JGF: return "Judgment Failed";
default : return "Unknown Result";
}
}
inline std::string get_type_from_lang(std::string lang) {
lang = upgraded_lang(lang);
if (lang == "Python2.7") {
return "python2.7";
} else if (lang == "Python3") {
return "python3";
} else if (lang == "Java8") {
return "java8";
} else if (lang == "Java11") {
return "java11";
} else if (lang == "Java17") {
return "java17";
} else {
return "default";
}
}
struct result {
static std::string result_file_name;
RS_TYPE type;
std::string extra;
int ust, usm;
int exit_code;
result() = default;
result(RS_TYPE type, std::string extra, int ust = -1, int usm = -1, int exit_code = -1)
: type(type), extra(extra), ust(ust), usm(usm), exit_code(exit_code) {
if (this->type != RS_AC) {
this->ust = -1, this->usm = -1;
}
}
static result failed_result() {
result res;
res.type = RS_JGF;
res.ust = -1;
res.usm = -1;
return res;
}
static result from_file(const std::string &file_name) {
result res;
FILE *fres = fopen(file_name.c_str(), "r");
if (!fres) {
return result::failed_result();
}
int type;
if (fscanf(fres, "%d %d %d %d\n", &type, &res.ust, &res.usm, &res.exit_code) != 4) {
fclose(fres);
return result::failed_result();
}
res.type = (RS_TYPE)type;
int L = 1 << 15;
char buf[L];
while (!feof(fres)) {
int c = fread(buf, 1, L, fres);
res.extra.append(buf, c);
if (ferror(fres)) {
fclose(fres);
return result::failed_result();
}
}
fclose(fres);
return res;
}
[[noreturn]] void dump_and_exit() {
FILE *f;
if (result_file_name == "stdout") {
f = stdout;
} else if (result_file_name == "stderr") {
f = stderr;
} else {
f = fopen(result_file_name.c_str(), "w");
}
fprintf(f, "%d %d %d %d\n", this->type, this->ust, this->usm, this->exit_code);
fprintf(f, "%s\n", this->extra.c_str());
if (f != stdout && f != stderr) {
fclose(f);
}
exit(this->type == RS_JGF ? 1 : 0);
}
};
std::string result::result_file_name("stdout");
template <typename T1, typename T2>
inline std::ostream& add_runp_arg(std::ostream &out, const std::pair<T1, std::vector<T2>> &arg) {
for (const auto &t : arg.second) {
out << " --" << arg.first << "=" << escapeshellarg(t);
}
return out;
}
template <typename T1, typename T2>
inline std::ostream& add_runp_arg(std::ostream &out, const std::pair<T1, T2> &arg) {
return out << " --" << arg.first << "=" << escapeshellarg(arg.second);
}
inline std::ostream& add_runp_arg(std::ostream &out, const std::vector<std::string> &arg) {
for (const auto &t : arg) {
out << " " << escapeshellarg(t);
}
return out;
}
inline std::ostream& add_runp_arg(std::ostream &out, const std::string &arg) {
return out << " " << escapeshellarg(arg);
}
struct config {
std::vector<std::string> readable_file_names; // other than stdin
std::vector<std::string> writable_file_names; // other than stdout, stderr
std::string result_file_name;
std::string input_file_name;
std::string output_file_name;
std::string error_file_name = "/dev/null";
std::string type = "default";
std::string work_path;
limits_t limits;
std::string program_name;
std::vector<std::string> rest_args;
// full args (possbily with interpreter)
std::vector<std::string> full_args;
bool unsafe = false;
bool allow_proc = false;
bool need_show_trace_details = false;
config(std::string program_name = "", const std::vector<std::string> &rest_args = {})
: program_name(program_name), rest_args(rest_args) {
}
config &set_type(const std::string &type) {
this->type = type;
return *this;
}
std::string get_cmd() const {
std::ostringstream sout;
sout << escapeshellarg(run_path / "run_program");
if (this->need_show_trace_details) {
add_runp_arg(sout, "--show-trace-details");
}
add_runp_arg(sout, std::make_pair("res", this->result_file_name));
add_runp_arg(sout, std::make_pair("in", this->input_file_name));
add_runp_arg(sout, std::make_pair("out", this->output_file_name));
add_runp_arg(sout, std::make_pair("err", this->error_file_name));
add_runp_arg(sout, std::make_pair("type", this->type));
// limits
add_runp_arg(sout, std::make_pair("tl", this->limits.time));
add_runp_arg(sout, std::make_pair("ml", this->limits.memory));
add_runp_arg(sout, std::make_pair("ol", this->limits.output));
if (this->limits.real_time != -1) {
add_runp_arg(sout, std::make_pair("rtl", this->limits.real_time));
}
if (this->limits.stack != -1) {
add_runp_arg(sout, std::make_pair("sl", this->limits.stack));
}
if (this->unsafe) {
add_runp_arg(sout, "--unsafe");
}
if (this->allow_proc) {
add_runp_arg(sout, "--allow-proc");
}
if (!this->work_path.empty()) {
add_runp_arg(sout, std::make_pair("work-path", this->work_path));
}
add_runp_arg(sout, std::make_pair("add-readable", this->readable_file_names));
add_runp_arg(sout, std::make_pair("add-writable", this->writable_file_names));
add_runp_arg(sout, this->program_name);
add_runp_arg(sout, this->rest_args);
return sout.str();
}
void gen_full_args() {
// assume that current_path() == work_path
full_args.clear();
full_args.push_back(program_name);
full_args.insert(full_args.end(), rest_args.begin(),rest_args.end());
if (type == "java8" || type == "java11" || type == "java17") {
full_args[0] = get_class_name_from_file(fs::path(full_args[0]) / ".main_class_name");
std::string jdk;
if (type == "java8") {
jdk = UOJ_OPEN_JDK8;
} else if (type == "java11") {
jdk = UOJ_OPEN_JDK11;
} else { // if (type == "java17") {
jdk = UOJ_OPEN_JDK17;
}
full_args.insert(full_args.begin(), {
fs::canonical(fs::path(jdk) / "bin" / "java"), "-Xmx2048m", "-Xss1024m",
"-XX:ActiveProcessorCount=1",
"-classpath", program_name
});
} else if (type == "python2.7") {
full_args.insert(full_args.begin(), {
UOJ_PYTHON2_7, "-E", "-s", "-B"
});
} else if (type == "python3") {
full_args.insert(full_args.begin(), {
UOJ_PYTHON3, "-I", "-B"
});
}
}
};
}
namespace runp::interaction {
struct pipe_config {
int from, from_fd;
int to, to_fd;
std::string saving_file_name;
pipe_config() = default;
pipe_config(int _from, int _from_fd, int _to, int _to_fd, const std::string &_saving_file_name = "")
: from(_from), from_fd(_from_fd), to(_to), to_fd(_to_fd), saving_file_name(_saving_file_name) {}
pipe_config(const std::string &str) {
if (sscanf(str.c_str(), "%d:%d-%d:%d", &from, &from_fd, &to, &to_fd) != 4) {
throw std::invalid_argument("bad init str for pipe");
}
}
};
struct config {
std::vector<std::string> cmds;
std::vector<pipe_config> pipes;
std::string get_cmd() const {
std::ostringstream sout;
sout << escapeshellarg(run_path / "run_interaction");
for (auto &cmd : cmds) {
sout << " " << escapeshellarg(cmd);
}
for (auto &pipe : pipes) {
sout << " " << "-p";
sout << " " << pipe.from << ":" << pipe.from_fd;
sout << "-" << pipe.to << ":" << pipe.to_fd;
if (!pipe.saving_file_name.empty()) {
sout << " " << "-s";
sout << " " << escapeshellarg(pipe.saving_file_name);
}
}
return sout.str();
}
};
/*
* @return interaction return value
**/
int run(const config &ric) {
return execute(ric.get_cmd().c_str());
}
}

View File

@ -0,0 +1,332 @@
#include <iostream>
#include <sstream>
#include <string>
#include <cstring>
#ifdef _MSC_VER
# define UOJ_NORETURN __declspec(noreturn)
#elif defined __GNUC__
# define UOJ_NORETURN __attribute__ ((noreturn))
#else
# define UOJ_NORETURN
#endif
namespace {
typedef unsigned char u8;
typedef unsigned u32;
typedef unsigned long long u64;
using namespace std;
struct sha256_t {
u8 sum[32];
string to_str() {
return string((char*)sum, 32);
}
};
inline u32 uoj_sha2_rotr(u32 x, int n) {
return x >> n | x << (32 - n);
}
void uoj_sha256_chunk(u8 *chunk, u32 *hs) {
static const u32 k[] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
u32 w[64];
for (int i = 0; i < 16; i++) {
w[i] = ((u32) chunk[i << 2 | 3])
| ((u32) chunk[i << 2 | 2] << 8)
| ((u32) chunk[i << 2 | 1] << 16)
| ((u32) chunk[i << 2 ] << 24);
}
for (int i = 16; i < 64; i++) {
u32 s0 = uoj_sha2_rotr(w[i - 15], 7) ^ uoj_sha2_rotr(w[i - 15], 18) ^ (w[i - 15] >> 3);
u32 s1 = uoj_sha2_rotr(w[i - 2], 17) ^ uoj_sha2_rotr(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
u32 a = hs[0], b = hs[1], c = hs[2], d = hs[3], e = hs[4], f = hs[5], g = hs[6], h = hs[7];
for (int i = 0; i < 64; i++) {
u32 s1 = uoj_sha2_rotr(e, 6) ^ uoj_sha2_rotr(e, 11) ^ uoj_sha2_rotr(e, 25);
u32 ch = (e & f) ^ (~e & g);
u32 temp1 = h + s1 + ch + k[i] + w[i];
u32 s0 = uoj_sha2_rotr(a, 2) ^ uoj_sha2_rotr(a, 13) ^ uoj_sha2_rotr(a, 22);
u32 maj = (a & b) ^ (a & c) ^ (b & c);
u32 temp2 = s0 + maj;
h = g;
g = f;
f = e;
e = d + temp1;
d = c;
c = b;
b = a;
a = temp1 + temp2;
}
hs[0] += a, hs[1] += b, hs[2] += c, hs[3] += d, hs[4] += e, hs[5] += f, hs[6] += g, hs[7] += h;
}
sha256_t uoj_sha256(int n, u8 *m) {
u32 hs[] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
u64 len = n * 8;
int r_n = 0;
u8 r[128];
for (int i = 0; i < n; i += 64) {
if (i + 64 <= n) {
uoj_sha256_chunk(m + i, hs);
} else {
for (int j = i; j < n; j++) {
r[r_n++] = m[j];
}
}
}
r[r_n++] = 0x80;
while ((r_n + 8) % 64 != 0) {
r[r_n++] = 0;
}
for (int i = 1; i <= 8; i++) {
r[r_n++] = len >> (64 - i * 8);
}
for (int i = 0; i < r_n; i += 64) {
uoj_sha256_chunk(r + i, hs);
}
sha256_t sum;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 4; j++) {
sum.sum[i << 2 | j] = hs[i] >> (32 - (j + 1) * 8);
}
}
return sum;
}
sha256_t uoj_sha256(const string &m) {
return uoj_sha256((int)m.length(), (u8*)m.data());
}
sha256_t uoj_hmac(const string &k, const string &m) {
string ki = k, ko = k;
for (int i = 0; i < (int)k.length(); i++) {
ki[i] ^= 0x36;
ko[i] ^= 0x5c;
}
return uoj_sha256(ko + uoj_sha256(ki + m).to_str());
}
class uoj_mt_rand_engine {
static const int N = 312;
static const int M = 156;
static const int R = 31;
static const u64 LM = (1llu << R) - 1;
static const u64 UM = ~LM;
static const u64 F = 6364136223846793005llu;
u64 mt[N];
int index;
void init(u64 seed) {
index = N;
mt[0] = seed;
for (int i = 1; i < N; i++) {
mt[i] = F * (mt[i - 1] ^ (mt[i - 1] >> 62)) + i;
}
}
void twist() {
for (int i = 0; i < N; i++) {
u64 x = (mt[i] & UM) + (mt[(i + 1) % N] & LM);
u64 xA = x >> 1;
if (x & 1) {
xA ^= 0xb5026f5aa96619e9llu;
}
mt[i] = mt[(i + M) % N] ^ xA;
}
index = 0;
}
public:
uoj_mt_rand_engine(u64 seed) {
init(seed);
}
uoj_mt_rand_engine(const string &s) {
sha256_t sum = uoj_sha256(s);
u64 seed = 0;
for (int i = 0; i < 8; i++)
seed = seed << 8 | sum.sum[i];
init(seed);
}
u64 next() {
if (index >= N) {
twist();
}
u64 y = mt[index];
y ^= (y >> 29) & 0x5555555555555555llu;
y ^= (y << 17) & 0x71d67fffeda60000llu;
y ^= (y << 37) & 0xfff7eee000000000llu;
y ^= y >> 43;
index++;
return y;
}
string randstr(int n, string charset="0123456789abcdefghijklmnopqrstuvwxyz") {
string s;
for (int i = 0; i < n; i++) {
s += charset[next() % charset.length()];
}
return s;
}
};
class uoj_cipher {
string key;
public:
uoj_cipher() {}
uoj_cipher(const string &_key) : key(_key) {}
void set_key(const string &_key) {
key = _key;
}
void encrypt(string &m) {
uoj_mt_rand_engine rnd(key);
string hmac = uoj_hmac(key, m).to_str();
m.push_back(0x80);
while ((m.length() + 32) % 512 != 0) {
m.push_back(0x00);
}
m += hmac;
for (int i = 0; i < (int)m.length(); i += 8) {
u64 r = rnd.next();
for (int j = i; j < i + 4; j++) {
m[j] = (u8)m[j] ^ (u8)r;
r >>= 16;
}
}
}
bool decrypt(string &m) {
uoj_mt_rand_engine rnd(key);
if (m.empty() || m.length() % 512 != 0) {
return false;
}
for (int i = 0; i < (int)m.length(); i += 8) {
u64 r = rnd.next();
for (int j = i; j < i + 4; j++) {
m[j] = (u8)m[j] ^ (u8)r;
r >>= 16;
}
}
string hmac = m.substr(m.length() - 32);
int len = m.length() - 33;
while (len >= 0 && (u8)m[len] != 0x80) {
len--;
}
if (len < 0) {
return false;
}
m.resize(len);
if (uoj_hmac(key, m).to_str() != hmac) {
return false;
}
return true;
}
};
class uoj_secure_io {
FILE fake_f, true_outf;
string input_m;
string key;
uoj_cipher cipher;
public:
istringstream in;
ostringstream out;
uoj_secure_io() {
srand(time(NULL));
const int BUFFER_SIZE = 1024;
u8 buffer[BUFFER_SIZE + 1];
while (!feof(stdin)) {
int ret = fread(buffer, 1, BUFFER_SIZE, stdin);
if (ret < 0) {
break;
}
input_m.append((char *)buffer, ret);
}
fclose(stdin);
for (int i = 0; i < (int)sizeof(fake_f); i++)
((u8*)&fake_f)[i] = rand();
memcpy(&true_outf, stdout, sizeof(FILE));
memcpy(stdout, &fake_f, sizeof(FILE));
}
void init_with_key(const string &_key) {
cerr.tie(NULL);
key = _key;
cipher.set_key(key);
if (!cipher.decrypt(input_m)) {
end("Unauthorized input");
}
in.str(input_m);
}
string input() {
return input_m;
}
UOJ_NORETURN void end(string m) {
memcpy(stdout, &true_outf, sizeof(FILE));
if (!out.str().empty()) {
if (m.empty()) {
m = out.str();
} else {
m = out.str() + m;
}
}
cipher.encrypt(m);
fwrite(m.data(), 1, m.length(), stdout);
fclose(stdout);
exit(0);
}
UOJ_NORETURN void end() {
end("");
}
};
}

View File

@ -1,22 +1,20 @@
#include "uoj_judger.h"
#include "uoj_judger_v2.h"
int main(int argc, char **argv) {
main_judger_init(argc, argv);
RunResult res = run_program(
(result_path + "/run_judger_result.txt").c_str(),
"/dev/null",
"/dev/null",
"stderr",
conf_run_limit("judger", 0, RL_JUDGER_DEFAULT),
"--unsafe",
conf_str("judger").c_str(),
main_path.c_str(),
work_path.c_str(),
result_path.c_str(),
data_path.c_str(),
NULL);
if (res.type != RS_AC) {
end_judge_judgement_failed("Judgement Failed : Judger " + info_str(res));
runp::config rpc(conf_str("judger"), {
main_path, work_path, result_path, data_path
});
rpc.result_file_name = result_path / "run_judger_result.txt";
rpc.input_file_name = "/dev/null";
rpc.output_file_name = "/dev/null";
rpc.error_file_name = "stderr";
rpc.limits = conf_run_limit("judger", 0, RL_JUDGER_DEFAULT);
rpc.unsafe = true;
runp::result res = runp::run(rpc);
if (res.type != runp::RS_AC) {
end_judge_judgment_failed("Judgment Failed : Judger " + runp::rstype_str(res.type));
}
return 0;
}

View File

@ -0,0 +1,491 @@
#include <iostream>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <utility>
#include <stdexcept>
#include <argp.h>
#include "uoj_run.h"
namespace fs = std::filesystem;
class language_not_supported_error : public std::invalid_argument {
public:
explicit language_not_supported_error()
: std::invalid_argument("This language has not been supported yet.") {}
};
class fail_to_read_src_error : public std::runtime_error {
public:
explicit fail_to_read_src_error(const std::string &what = "An error occurs when trying to read the source code.")
: std::runtime_error(what) {}
};
class compile_error : public std::invalid_argument {
public:
explicit compile_error(const std::string &what)
: std::invalid_argument(what) {}
};
const std::vector<std::pair<const char *, const char *>> suffix_search_list = {
{".code" , "" },
{"20.cpp" , "C++20" },
{"17.cpp" , "C++17" },
{"14.cpp" , "C++14" },
{"11.cpp" , "C++11" },
{".cpp" , "C++" },
{".c" , "C" },
{".pas" , "Pascal" },
{"2.7.py" , "Python2.7"},
{".py" , "Python3" },
{"7.java" , "Java7" },
{"8.java" , "Java8" },
{"11.java", "Java11" },
{"14.java", "Java14" },
{"17.java", "Java17" },
};
struct compile_config {
std::string name;
std::string src;
std::string lang = "auto";
std::string opt;
std::string implementer;
std::string custom_compiler_path;
std::vector<std::string> cinclude_dirs;
void auto_find_src() {
if (!src.empty()) {
return;
}
for (auto &p : suffix_search_list) {
if (fs::is_regular_file(name + p.first)) {
src = fs::canonical(name + p.first);
if (lang == "auto") {
lang = p.second;
}
return;
}
}
}
};
error_t compile_argp_parse_opt(int key, char *arg, struct argp_state *state) {
compile_config *config = (compile_config*)state->input;
try {
switch (key) {
case 's':
config->src = arg;
break;
case 'i':
config->implementer = arg;
break;
case 'l':
config->lang = arg;
break;
case 'c':
config->custom_compiler_path = arg;
break;
case 'I':
config->cinclude_dirs.push_back(arg);
break;
case ARGP_KEY_ARG:
config->name = arg;
break;
case ARGP_KEY_END:
if (state->arg_num != 1) {
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
} catch (std::exception &e) {
argp_usage(state);
}
return 0;
}
compile_config parse_args(int argc, char **argv) {
argp_option argp_options[] = {
{"src" , 's', "SOURCE_CODE" , 0, "set the path to source code" , 1},
{"impl" , 'i', "IMPLEMENTER" , 0, "set the implementer name" , 2},
{"lang" , 'l', "LANGUAGE" , 0, "set the language" , 3},
{"custom" , 'c', "CUSTOM" , 0, "path to custom compilers (those are not placed in /usr/bin)", 4},
{"cinclude", 'I', "DIRECTORY" , 0, "add the directory dir to the list of directories to be searched for header files during preprocessing (for C/C++)", 5},
{0}
};
char argp_args_doc[] = "name";
char argp_doc[] = "compile: a tool to compile programs";
argp compile_argp = {
argp_options,
compile_argp_parse_opt,
argp_args_doc,
argp_doc
};
compile_config config;
argp_parse(&compile_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config);
config.auto_find_src();
if (config.src.empty() || !fs::is_regular_file(config.src)) {
throw fail_to_read_src_error();
}
return config;
}
bool is_illegal_keyword(const std::string &name) {
return name == "__asm" || name == "__asm__" || name == "asm";
}
bool has_illegal_keywords_in_file(const std::string &src) {
std::ifstream fin(src);
if (!fin) {
throw fail_to_read_src_error();
}
const int L = 1 << 15;
char buf[L];
std::string key;
while (!fin.eof()) {
fin.read(buf, L);
int cnt = fin.gcount();
for (char *p = buf; p != buf + cnt; p++) {
char c = *p;
if (isalnum(c) || c == '_') {
if (key.size() < 20) {
key += c;
} else {
if (is_illegal_keyword(key)) {
return true;
}
key.erase(key.begin());
key += c;
}
}
else {
if (is_illegal_keyword(key)) {
return true;
}
key.clear();
}
}
if (fin.bad()) {
throw fail_to_read_src_error();
}
}
return false;
}
std::string get_java_main_class(const std::string &src) {
std::ifstream fin(src);
if (!fin) {
throw fail_to_read_src_error();
}
const int L = 1 << 15;
char buf[L];
std::string s;
int mode = 0;
while (!fin.eof()) {
fin.read(buf, L);
int cnt = fin.gcount();
for (char *p = buf; p != buf + cnt; p++) {
s += *p;
switch (mode) {
case 0:
switch (*p) {
case '/':
mode = 1;
break;
case '\'':
mode = 5;
break;
case '\"':
mode = 6;
break;
}
break;
case 1:
switch (*p) {
case '/':
mode = 2;
s.pop_back();
s.pop_back();
break;
case '*':
mode = 3;
s.pop_back();
s.pop_back();
break;
}
break;
case 2:
s.pop_back();
switch (*p) {
case '\n':
s += '\n';
mode = 0;
break;
}
break;
case 3:
s.pop_back();
switch (*p) {
case '*':
mode = 4;
break;
}
break;
case 4:
s.pop_back();
switch (*p) {
case '/':
s += ' ';
mode = 0;
break;
}
break;
case 5:
switch (*p) {
case '\'':
mode = 0;
break;
case '\\':
mode = 7;
break;
}
case 6:
switch (*p) {
case '\"':
mode = 0;
break;
case '\\':
mode = 8;
break;
}
case 7:
mode = 5;
break;
case 8:
mode = 6;
break;
}
}
if (fin.bad()) {
throw fail_to_read_src_error();
}
}
bool valid[256];
std::fill(valid, valid + 256, false);
std::fill(valid + 'a', valid + 'z' + 1, true);
std::fill(valid + 'A', valid + 'Z' + 1, true);
std::fill(valid + '0', valid + '9' + 1, true);
valid['.'] = true;
valid['_'] = true;
std::vector<std::string> tokens;
for (size_t p = 0, np = 0; p < s.length(); p = np) {
while (np < s.length() && valid[(unsigned char)s[np]]) {
np++;
}
if (np == p) {
np++;
} else {
tokens.push_back(s.substr(p, np - p));
}
}
if (tokens.size() > 0 && tokens[0] == "package") {
throw compile_error("Please don't specify the package.");
}
for (size_t i = 0; i + 1 < tokens.size(); i++) {
if (tokens[i] == "class") {
std::string name = tokens[i + 1];
if (name.length() > 100) {
throw compile_error("The name of the main class is too long.");
}
for (size_t k = 0; k < name.length(); k++) {
if (!isalnum(name[k]) && name[k] != '_') {
throw compile_error("The name of the main class should only contain letters, numbers and underscore sign.");
}
}
if (!isalpha(name[0])) {
throw compile_error("The name of the main class cannot begin with a number.");
}
return tokens[i + 1];
}
}
throw compile_error("Cannot find the main class.");
}
int compile_cpp(const compile_config &conf, const std::string &std) {
std::ostringstream sflags;
spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE", "-std=" + std);
for (auto dir : conf.cinclude_dirs) {
add_spaced_out(sflags, "-I" + dir);
}
if (conf.implementer.empty()) {
return execute(
UOJ_GPLUSPLUS, sflags.str(), "-o", conf.name,
"-x", "c++", conf.src
);
} else {
return execute(
UOJ_GPLUSPLUS, sflags.str(), "-o", conf.name,
conf.implementer + ".cpp", "-x", "c++", conf.src
);
}
}
int compile_c(const compile_config &conf) {
std::ostringstream sflags;
spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE");
for (auto dir : conf.cinclude_dirs) {
add_spaced_out(sflags, "-I" + dir);
}
if (conf.implementer.empty()) {
return execute(
UOJ_GCC, sflags.str(), "-o", conf.name,
"-x", "c", conf.src
);
} else {
return execute(
UOJ_GCC, sflags.str(), "-o", conf.name,
conf.implementer + ".c", "-x", "c", conf.src
);
}
}
int compile_python2_7(const compile_config &conf) {
if (!conf.implementer.empty()) {
throw language_not_supported_error();
}
std::string dfile = "__pycode__/" + conf.name + ".py";
std::string compiler_code =
"import py_compile\n"
"import sys\n"
"try:\n"
" py_compile.compile('" + conf.src + "', cfile='" + conf.name + "', dfile='" + dfile + "', doraise=True)\n"
" sys.exit(0)\n"
"except Exception as e:\n"
" print e\n"
" sys.exit(1)\n";
return execute(UOJ_PYTHON2_7, "-E", "-s", "-B", "-O", "-c", escapeshellarg(compiler_code));
}
int compile_python3(const compile_config &conf) {
if (!conf.implementer.empty()) {
throw language_not_supported_error();
}
std::string dfile = "__pycode__/" + conf.name + ".py";
std::string compiler_code =
"import py_compile\n"
"import sys\n"
"try:\n"
" py_compile.compile('" + conf.src + "', cfile='" + conf.name + "', dfile='" + dfile + "', doraise=True)\n"
" sys.exit(0)\n"
"except Exception as e:\n"
" print(e)\n"
" sys.exit(1)\n";
return execute(UOJ_PYTHON3, "-I", "-B", "-O", "-c", escapeshellarg(compiler_code));
}
int compile_java(const compile_config &conf, const std::string &jdk) {
if (!conf.implementer.empty()) {
throw language_not_supported_error();
}
try {
std::string main_class = get_java_main_class(conf.src);
fs::remove_all(conf.name);
fs::create_directory(conf.name);
fs::copy_file(conf.src, fs::path(conf.name) / (main_class + ".java"));
int ret = execute(
"cd", conf.name,
"&&", jdk + "/bin/javac", main_class + ".java"
);
fs::remove(fs::path(conf.name) / (main_class + ".java"));
put_class_name_to_file(fs::path(conf.name) / ".main_class_name", main_class);
return ret;
} catch (std::system_error &e) {
throw compile_error("System Error");
}
}
int compile_pas(const compile_config &conf) {
if (conf.implementer.empty()) {
return execute(UOJ_FPC, conf.src, "-O2");
} else {
try {
std::string unit_name = get_class_name_from_file(conf.name + ".unit_name");
if (!unit_name.empty()) {
fs::copy_file(conf.src, unit_name + ".pas");
}
int ret = execute(UOJ_FPC, conf.implementer + ".pas", "-o" + conf.name, "-O2");
if (!unit_name.empty()) {
fs::remove(unit_name + ".pas");
}
return ret;
} catch (std::system_error &e) {
throw compile_error("System Error");
}
}
}
int compile(const compile_config &conf) {
if ((conf.lang.length() > 0 && conf.lang[0] == 'C') && has_illegal_keywords_in_file(conf.src)) {
std::cerr << "Compile Failed: assembly language detected" << std::endl;
return 1;
}
std::string lang = upgraded_lang(conf.lang);
if (lang == "C++" || lang == "C++03") {
return compile_cpp(conf, "c++03");
} else if (lang == "C++11") {
return compile_cpp(conf, "c++11");
} else if (lang == "C++14") {
return compile_cpp(conf, "c++14");
} else if (lang == "C++17") {
return compile_cpp(conf, "c++17");
} else if (lang == "C++20") {
return compile_cpp(conf, "c++20");
} else if (lang == "C") {
return compile_c(conf);
} else if (lang == "Python2.7") {
return compile_python2_7(conf);
} else if (lang == "Python3") {
return compile_python3(conf);
} else if (lang == "Java8") {
return compile_java(conf, UOJ_OPEN_JDK8);
} else if (lang == "Java11") {
return compile_java(conf, UOJ_OPEN_JDK11);
} else if (lang == "Java17") {
return compile_java(conf, UOJ_OPEN_JDK17);
} else if (lang == "Pascal") {
return compile_pas(conf);
} else {
throw language_not_supported_error();
}
}
int main(int argc, char **argv) {
try {
return compile(parse_args(argc, argv));
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
return 1;
}
}

View File

@ -16,62 +16,38 @@
#include <vector>
#include <set>
#include <argp.h>
#include "uoj_env.h"
#include "uoj_run.h"
using namespace std;
struct RunResult {
int result;
int ust;
int usm;
int exit_code;
RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1)
: result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) {
if (result != RS_AC) {
ust = -1, usm = -1;
}
}
};
struct PipeConfig {
int from, to;
int from_fd, to_fd;
string saving_file_name; // empty for none
PipeConfig() {
}
PipeConfig(string str) {
if (sscanf(str.c_str(), "%d:%d-%d:%d", &from, &from_fd, &to, &to_fd) != 4) {
throw invalid_argument("bad init str for PipeConfig");
}
from -= 1;
to -= 1;
}
};
struct RunInteractionConfig {
vector<string> cmds;
vector<PipeConfig> pipes;
};
struct RunCmdData {
struct run_cmd_data {
string cmd;
pid_t pid;
vector<int> ipipes, opipes;
};
struct PipeData {
PipeConfig config;
struct pipe_data {
runp::interaction::pipe_config config;
int ipipefd[2], opipefd[2];
thread io_thread;
exception_ptr eptr;
};
class RunInteraction {
void write_all_or_throw(int fd, char *buf, int n) {
int wcnt = 0;
while (wcnt < n) {
int ret = write(fd, buf + wcnt, n - wcnt);
if (ret == -1) {
throw system_error(errno, system_category());
}
wcnt += ret;
}
}
class interaction_runner {
private:
vector<RunCmdData> cmds;
vector<PipeData> pipes;
vector<run_cmd_data> cmds;
vector<pipe_data> pipes;
void prepare_fd() { // me
for (int i = 0; i < (int)pipes.size(); i++) {
@ -80,15 +56,21 @@ private:
}
}
void prepare_fd_for_cmd(int id) {
freopen("/dev/null", "r", stdin);
freopen("/dev/null", "w", stdout);
freopen("/dev/null", "w", stderr);
if (freopen("/dev/null", "r", stdin) == NULL) {
throw system_error(errno, system_category());
}
if (freopen("/dev/null", "w", stdout) == NULL) {
throw system_error(errno, system_category());
}
if (freopen("/dev/null", "w", stderr) == NULL) {
throw system_error(errno, system_category());
}
for (int i = 0; i < (int)pipes.size(); i++) {
if (pipes[i].config.from == id) {
if (pipes[i].config.from - 1 == id) {
dup2(pipes[i].ipipefd[1], 128 + pipes[i].ipipefd[1]);
}
if (pipes[i].config.to == id) {
if (pipes[i].config.to - 1 == id) {
dup2(pipes[i].opipefd[0], 128 + pipes[i].opipefd[0]);
}
close(pipes[i].ipipefd[0]);
@ -97,11 +79,11 @@ private:
close(pipes[i].opipefd[1]);
}
for (int i = 0; i < (int)pipes.size(); i++) {
if (pipes[i].config.from == id) {
if (pipes[i].config.from - 1 == id) {
dup2(128 + pipes[i].ipipefd[1], pipes[i].config.from_fd);
close(128 + pipes[i].ipipefd[1]);
}
if (pipes[i].config.to == id) {
if (pipes[i].config.to - 1 == id) {
dup2(128 + pipes[i].opipefd[0], pipes[i].config.to_fd);
close(128 + pipes[i].opipefd[0]);
}
@ -109,38 +91,54 @@ private:
}
void wait_pipe_io(int pipe_id) {
FILE *sf = NULL;
if (!pipes[pipe_id].config.saving_file_name.empty())
FILE *sf = nullptr;
if (!pipes[pipe_id].config.saving_file_name.empty()) {
sf = fopen(pipes[pipe_id].config.saving_file_name.c_str(), "w");
}
int ifd = pipes[pipe_id].ipipefd[0];
int ofd = pipes[pipe_id].opipefd[1];
int sfd = sf ? fileno(sf) : -1;
FILE *inf = fdopen(ifd, "r");
FILE *ouf = fdopen(ofd, "w");
try {
pipes[pipe_id].eptr = nullptr;
int iflags = fcntl(ifd, F_GETFL);
const int L = 4096;
char buf[L];
int sbuf_len = 0;
char sbuf[L * 2];
try {
pipes[pipe_id].eptr = nullptr;
while (true) {
int c = fgetc(inf);
if (c == EOF) {
if (errno) {
int cnt1 = read(ifd, buf, 1);
if (cnt1 == -1) {
throw system_error(errno, system_category());
}
if (cnt1 == 0) {
break;
}
if (fputc(c, ouf) == EOF) {
fcntl(ifd, F_SETFL, iflags | O_NONBLOCK);
int cnt2 = read(ifd, buf + 1, L - 1);
fcntl(ifd, F_SETFL, iflags);
if (cnt2 == -1) {
if (errno != EAGAIN) {
throw system_error(errno, system_category());
}
fflush(ouf);
cnt2 = 0;
}
if (fputc(c, sf) == EOF) {
throw system_error(errno, system_category());
write_all_or_throw(ofd, buf, cnt2 + 1);
if (sf) {
memcpy(sbuf + sbuf_len, buf, cnt2 + 1);
sbuf_len += cnt2 + 1;
if (sbuf_len > L) {
write_all_or_throw(sfd, sbuf, sbuf_len);
sbuf_len = 0;
}
}
}
} catch (exception &e) {
@ -148,12 +146,19 @@ private:
pipes[pipe_id].eptr = current_exception();
}
if (sf) {
if (sbuf_len > 0) {
write_all_or_throw(sfd, sbuf, sbuf_len);
sbuf_len = 0;
}
fclose(sf);
fclose(inf);
fclose(ouf);
}
close(ifd);
close(ofd);
}
public:
RunInteraction(const RunInteractionConfig &config) {
interaction_runner(const runp::interaction::config &config) {
cmds.resize(config.cmds.size());
for (int i = 0; i < (int)config.cmds.size(); i++) {
cmds[i].cmd = config.cmds[i];
@ -162,8 +167,8 @@ public:
pipes.resize(config.pipes.size());
for (int i = 0; i < (int)config.pipes.size(); i++) {
pipes[i].config = config.pipes[i];
cmds[pipes[i].config.from].opipes.push_back(i);
cmds[pipes[i].config.to].ipipes.push_back(i);
cmds[pipes[i].config.from - 1].opipes.push_back(i);
cmds[pipes[i].config.to - 1].ipipes.push_back(i);
}
for (int i = 0; i < (int)pipes.size(); i++) {
@ -175,8 +180,8 @@ public:
cmds[i].pid = fork();
if (cmds[i].pid == 0) {
prepare_fd_for_cmd(i);
system(cmds[i].cmd.c_str());
exit(0);
execl("/bin/sh", "sh", "-c", cmds[i].cmd.c_str(), NULL);
exit(1); // exec failed, exit 1
} else if (cmds[i].pid == -1) {
throw system_error(errno, system_category());
}
@ -184,7 +189,7 @@ public:
prepare_fd();
for (int i = 0; i < (int)pipes.size(); i++) {
pipes[i].io_thread = thread(&RunInteraction::wait_pipe_io, this, i);
pipes[i].io_thread = thread(&interaction_runner::wait_pipe_io, this, i);
}
}
@ -199,12 +204,12 @@ public:
};
error_t run_interaction_argp_parse_opt (int key, char *arg, struct argp_state *state) {
RunInteractionConfig *config = (RunInteractionConfig*)state->input;
runp::interaction::config *config = (runp::interaction::config*)state->input;
try {
switch (key) {
case 'p':
config->pipes.push_back(PipeConfig(arg));
config->pipes.push_back(runp::interaction::pipe_config(arg));
break;
case 's':
if (config->pipes.empty()) {
@ -230,7 +235,7 @@ error_t run_interaction_argp_parse_opt (int key, char *arg, struct argp_state *s
return 0;
}
RunInteractionConfig parse_args(int argc, char **argv) {
runp::interaction::config parse_args(int argc, char **argv) {
argp_option run_interaction_argp_options[] = {
{"add-pipe" , 'p', "PIPE" , 0, "Add a pipe <from>:<fd>-<to>:<fd> (fd < 128)" , 1},
{"save-pipe" , 's', "FILE" , 0, "Set last pipe saving file" , 2},
@ -246,7 +251,7 @@ RunInteractionConfig parse_args(int argc, char **argv) {
run_interaction_argp_doc
};
RunInteractionConfig config;
runp::interaction::config config;
argp_parse(&run_interaction_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &config);
@ -256,7 +261,7 @@ RunInteractionConfig parse_args(int argc, char **argv) {
int main(int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
RunInteraction ri(parse_args(argc, argv));
interaction_runner ri(parse_args(argc, argv));
ri.join();
return 0;

View File

@ -1,86 +1,31 @@
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <asm/unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/user.h>
#include <fcntl.h>
#include <cstring>
#include <string>
#include <vector>
#include <set>
#include <argp.h>
#include "uoj_env.h"
using namespace std;
#include "run_program_sandbox.h"
struct RunResult {
int result;
int ust;
int usm;
int exit_code;
RunResult(int _result, int _ust = -1, int _usm = -1, int _exit_code = -1)
: result(_result), ust(_ust), usm(_usm), exit_code(_exit_code) {
if (result != RS_AC) {
ust = -1, usm = -1;
}
}
enum RUN_EVENT_TYPE {
ET_SKIP,
ET_EXIT,
ET_SIGNALED,
ET_REAL_TLE,
ET_USER_CPU_TLE,
ET_MLE,
ET_OLE,
ET_SECCOMP_STOP,
ET_SIGNAL_DELIVERY_STOP,
ET_RESTART,
};
struct RunProgramConfig
{
int time_limit;
int real_time_limit;
int memory_limit;
int output_limit;
int stack_limit;
string input_file_name;
string output_file_name;
string error_file_name;
string result_file_name;
string work_path;
string type;
vector<string> extra_readable_files;
vector<string> extra_writable_files;
bool allow_proc;
bool safe_mode;
bool need_show_trace_details;
struct run_event {
RUN_EVENT_TYPE type;
int pid = -1;
rp_child_proc *cp;
string program_name;
string program_basename;
vector<string> argv;
int sig = 0;
int exitcode = 0;
int pevent = 0;
int usertim = 0, usermem = 0;
};
int put_result(string result_file_name, RunResult res) {
FILE *f;
if (result_file_name == "stdout") {
f = stdout;
} else if (result_file_name == "stderr") {
f = stderr;
} else {
f = fopen(result_file_name.c_str(), "w");
}
fprintf(f, "%d %d %d %d\n", res.result, res.ust, res.usm, res.exit_code);
if (f != stdout && f != stderr) {
fclose(f);
}
if (res.result == RS_JGF) {
return 1;
} else {
return 0;
}
}
char self_path[PATH_MAX + 1] = {};
#include "run_program_conf.h"
argp_option run_program_argp_options[] =
{
argp_option run_program_argp_options[] = {
{"tl" , 'T', "TIME_LIMIT" , 0, "Set time limit (in second)" , 1},
{"rtl" , 'R', "TIME_LIMIT" , 0, "Set real time limit (in second)" , 2},
{"ml" , 'M', "MEMORY_LIMIT", 0, "Set memory limit (in mb)" , 3},
@ -101,26 +46,24 @@ argp_option run_program_argp_options[] =
{"add-writable-raw" , 506, "FILE" , 0, "Add a writable (don't transform to its real path)" , 15},
{0}
};
error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state)
{
RunProgramConfig *config = (RunProgramConfig*)state->input;
error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state) {
runp::config *config = (runp::config*)state->input;
switch (key)
{
switch (key) {
case 'T':
config->time_limit = atoi(arg);
config->limits.time = atoi(arg);
break;
case 'R':
config->real_time_limit = atoi(arg);
config->limits.real_time = atoi(arg);
break;
case 'M':
config->memory_limit = atoi(arg);
config->limits.memory = atoi(arg);
break;
case 'O':
config->output_limit = atoi(arg);
config->limits.output = atoi(arg);
break;
case 'S':
config->stack_limit = atoi(arg);
config->limits.stack = atoi(arg);
break;
case 'i':
config->input_file_name = arg;
@ -132,10 +75,7 @@ error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state
config->error_file_name = arg;
break;
case 'w':
config->work_path = realpath(arg);
if (config->work_path.empty()) {
argp_usage(state);
}
config->work_path = arg;
break;
case 'r':
config->result_file_name = arg;
@ -144,10 +84,10 @@ error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state
config->type = arg;
break;
case 500:
config->extra_readable_files.push_back(realpath(arg));
config->readable_file_names.push_back(realpath(arg));
break;
case 501:
config->safe_mode = false;
config->unsafe = true;
break;
case 502:
config->need_show_trace_details = true;
@ -156,18 +96,18 @@ error_t run_program_argp_parse_opt (int key, char *arg, struct argp_state *state
config->allow_proc = true;
break;
case 504:
config->extra_readable_files.push_back(arg);
config->readable_file_names.push_back(arg);
break;
case 505:
config->extra_writable_files.push_back(realpath(arg));
config->writable_file_names.push_back(realpath_for_write(arg));
break;
case 506:
config->extra_writable_files.push_back(arg);
config->writable_file_names.push_back(arg);
break;
case ARGP_KEY_ARG:
config->argv.push_back(arg);
config->program_name = arg;
for (int i = state->next; i < state->argc; i++) {
config->argv.push_back(state->argv[i]);
config->rest_args.push_back(state->argv[i]);
}
state->next = state->argc;
break;
@ -191,54 +131,59 @@ argp run_program_argp = {
run_program_argp_doc
};
RunProgramConfig run_program_config;
void parse_args(int argc, char **argv) {
run_program_config.time_limit = 1;
run_program_config.real_time_limit = -1;
run_program_config.memory_limit = 256;
run_program_config.output_limit = 64;
run_program_config.stack_limit = 1024;
run_program_config.limits.time = 1;
run_program_config.limits.real_time = -1;
run_program_config.limits.memory = 256;
run_program_config.limits.output = 64;
run_program_config.limits.stack = 1024;
run_program_config.input_file_name = "stdin";
run_program_config.output_file_name = "stdout";
run_program_config.error_file_name = "stderr";
run_program_config.work_path = "";
run_program_config.result_file_name = "stdout";
run_program_config.type = "default";
run_program_config.safe_mode = true;
run_program_config.unsafe = false;
run_program_config.need_show_trace_details = false;
run_program_config.allow_proc = false;
argp_parse(&run_program_argp, argc, argv, ARGP_NO_ARGS | ARGP_IN_ORDER, 0, &run_program_config);
if (run_program_config.real_time_limit == -1)
run_program_config.real_time_limit = run_program_config.time_limit + 2;
run_program_config.stack_limit = min(run_program_config.stack_limit, run_program_config.memory_limit);
runp::result::result_file_name = run_program_config.result_file_name;
if (!run_program_config.work_path.empty()) {
if (chdir(run_program_config.work_path.c_str()) == -1) {
exit(put_result(run_program_config.result_file_name, RS_JGF));
}
if (run_program_config.limits.real_time == -1) {
run_program_config.limits.real_time = run_program_config.limits.time + 2;
}
run_program_config.limits.stack = min(run_program_config.limits.stack, run_program_config.limits.memory);
run_program_config.program_name = realpath(run_program_config.argv[0]);
// NOTE: program_name is the full path of the program, not just the file name (but can start with "./")
if (run_program_config.work_path.empty()) {
run_program_config.work_path = dirname(run_program_config.program_name);
run_program_config.program_basename = basename(run_program_config.program_name);
run_program_config.argv[0] = "./" + run_program_config.program_basename;
if (chdir(run_program_config.work_path.c_str()) == -1) {
exit(put_result(run_program_config.result_file_name, RS_JGF));
run_program_config.work_path = realpath(getcwd());
if (!is_len_valid_path(run_program_config.work_path)) {
// work path does not exist
runp::result(runp::RS_JGF, "error code: WPDNE1").dump_and_exit();
}
} else {
run_program_config.work_path = realpath(run_program_config.work_path);
if (!is_len_valid_path(run_program_config.work_path) || chdir(run_program_config.work_path.c_str()) == -1) {
// work path does not exist
runp::result(runp::RS_JGF, "error code: WPDNE2").dump_and_exit();
}
}
if (!is_len_valid_path(realpath(run_program_config.program_name))) {
// invalid program name
runp::result(runp::RS_JGF, "error code: INVPGN2").dump_and_exit();
}
if (!available_program_type_set.count(run_program_config.type)) {
// invalid program type
runp::result(runp::RS_JGF, "error code: INVPGT").dump_and_exit();
}
if (run_program_config.type == "python2") {
string pre[4] = {"/usr/bin/python2.7", "-E", "-s", "-B"};
run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 4);
} else if (run_program_config.type == "python3") {
string pre[3] = {"/usr/bin/python3.8", "-I", "-B"};
run_program_config.argv.insert(run_program_config.argv.begin(), pre, pre + 3);
try {
run_program_config.gen_full_args();
} catch (exception &e) {
// fail to generate full args
runp::result(runp::RS_JGF, "error code: GFULARGS").dump_and_exit();
}
}
@ -255,10 +200,20 @@ void set_limit(int r, int rcur, int rmax = -1) {
exit(55);
}
}
void run_child() {
set_limit(RLIMIT_CPU, run_program_config.time_limit, run_program_config.real_time_limit);
set_limit(RLIMIT_FSIZE, run_program_config.output_limit << 20);
set_limit(RLIMIT_STACK, run_program_config.stack_limit << 20);
void set_user_cpu_time_limit(int tl) {
struct itimerval val;
val.it_value = {tl, 100 * 1000};
val.it_interval = {0, 100 * 1000};
setitimer(ITIMER_VIRTUAL, &val, NULL);
}
[[noreturn]] void run_child() {
setpgid(0, 0);
set_limit(RLIMIT_FSIZE, run_program_config.limits.output << 20);
set_limit(RLIMIT_STACK, run_program_config.limits.stack << 20);
// TODO: use https://man7.org/linux/man-pages/man3/vlimit.3.html to limit virtual memory
if (run_program_config.input_file_name != "stdin") {
if (freopen(run_program_config.input_file_name.c_str(), "r", stdin) == NULL) {
@ -310,76 +265,374 @@ void run_child() {
setenv("SHELL", env_shell.c_str(), 1);
}
char **program_c_argv = new char*[run_program_config.argv.size() + 1];
for (size_t i = 0; i < run_program_config.argv.size(); i++) {
program_c_argv[i] = new char[run_program_config.argv[i].size() + 1];
strcpy(program_c_argv[i], run_program_config.argv[i].c_str());
char** program_c_argv = new char*[run_program_config.full_args.size() + 1];
for (size_t i = 0; i < run_program_config.full_args.size(); i++) {
program_c_argv[i] = run_program_config.full_args[i].data();
}
program_c_argv[run_program_config.argv.size()] = NULL;
program_c_argv[run_program_config.full_args.size()] = NULL;
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
exit(16);
}
if (execv(program_c_argv[0], program_c_argv) == -1) {
exit(17);
kill(getpid(), SIGSTOP);
if (!run_program_config.unsafe && !set_seccomp_bpf()) {
exit(99);
}
pid_t pid = fork();
if (pid == 0) {
set_user_cpu_time_limit(run_program_config.limits.time);
execv(program_c_argv[0], program_c_argv);
_exit(17);
} else if (pid != -1) {
int status;
while (wait(&status) > 0);
}
exit(17);
}
const int MaxNRPChildren = 50;
struct rp_child_proc {
pid_t pid;
int mode;
};
int n_rp_children;
// limit for the safe mode, an upper limit for the number of calls to fork/vfork/clone
const size_t MAX_TOTAL_RP_CHILDREN = 100;
size_t total_rp_children = 0;
struct timeval start_time;
struct timeval end_time;
pid_t rp_timer_pid;
rp_child_proc rp_children[MaxNRPChildren];
vector<rp_child_proc> rp_children;
struct rusage *ruse0p = NULL;
bool has_real_TLE() {
struct timeval elapsed;
timersub(&end_time, &start_time, &elapsed);
return elapsed.tv_sec >= run_program_config.limits.real_time;
}
int rp_children_pos(pid_t pid) {
for (int i = 0; i < n_rp_children; i++) {
for (size_t i = 0; i < rp_children.size(); i++) {
if (rp_children[i].pid == pid) {
return i;
return (int)i;
}
}
return -1;
}
int rp_children_add(pid_t pid) {
if (n_rp_children == MaxNRPChildren) {
return -1;
}
rp_children[n_rp_children].pid = pid;
rp_children[n_rp_children].mode = -1;
n_rp_children++;
return 0;
void rp_children_add(pid_t pid) {
rp_child_proc rpc;
rpc.pid = pid;
rpc.flags = CPF_STARTUP | CPF_IGNORE_ONE_SIGSTOP;
rp_children.push_back(rpc);
}
void rp_children_del(pid_t pid) {
int new_n = 0;
for (int i = 0; i < n_rp_children; i++) {
size_t new_n = 0;
for (size_t i = 0; i < rp_children.size(); i++) {
if (rp_children[i].pid != pid) {
rp_children[new_n++] = rp_children[i];
}
}
n_rp_children = new_n;
rp_children.resize(new_n);
}
string get_usage_summary(struct rusage *rusep) {
struct timeval elapsed;
timersub(&end_time, &start_time, &elapsed);
ostringstream sout;
struct timeval total_cpu;
timeradd(&rusep->ru_utime, &rusep->ru_stime, &total_cpu);
sout << "[statistics]" << endl;
sout << "user CPU / total CPU / elapsed real time: ";
sout << rusep->ru_utime.tv_sec * 1000 + rusep->ru_utime.tv_usec / 1000 << "ms / ";
sout << total_cpu.tv_sec * 1000 + total_cpu.tv_usec / 1000 << "ms / ";
sout << elapsed.tv_sec * 1000 + elapsed.tv_usec / 1000 << "ms." << endl;
sout << "max RSS: " << rusep->ru_maxrss << "kb." << endl;
sout << "total number of threads: " << total_rp_children + 1 << "." << endl;
sout << "voluntary / total context switches: " << rusep->ru_nvcsw << " / " << rusep->ru_nvcsw + rusep->ru_nivcsw << ".";
return sout.str();
}
void stop_child(pid_t pid) {
kill(pid, SIGKILL);
}
void stop_all() {
void stop_all(runp::result res) {
struct rusage tmp, ruse, *rusep = ruse0p;
kill(rp_timer_pid, SIGKILL);
for (int i = 0; i < n_rp_children; i++) {
kill(rp_children[i].pid, SIGKILL);
killpg(rp_children[0].pid, SIGKILL);
// in case some process changes its pgid
for (auto &rpc : rp_children) {
kill(rpc.pid, SIGKILL);
}
int stat;
while (true) {
pid_t pid = wait4(-1, &stat, __WALL, &tmp);
// cerr << "stop_all: wait " << pid << endl;
if (pid < 0) {
if (errno == EINTR) {
continue;
} else if (errno == ECHILD) {
break;
} else {
res.dump_and_exit();
}
}
if (pid != rp_timer_pid && pid != rp_children[0].pid) {
if (res.type != runp::RS_AC) {
if (rp_children.size() >= 2 && pid == rp_children[1].pid) {
ruse = tmp;
rusep = &ruse;
}
} else if (rp_children.size() >= 2 && pid != rp_children[1].pid) {
res = runp::result(runp::RS_RE, "main thread exited before others");
}
}
// it is possible that a newly created process hasn't been logged into rp_children
// kill it for safty
kill(pid, SIGKILL);
}
if (rusep) {
res.extra += "\n";
res.extra += get_usage_summary(rusep);
}
res.dump_and_exit();
}
run_event next_event() {
static struct rusage ruse;
static pid_t prev_pid = -1;
run_event e;
int stat = 0;
e.pid = wait4(-1, &stat, __WALL, &ruse);
const int wait_errno = errno;
gettimeofday(&end_time, NULL);
ruse0p = NULL;
if (e.pid < 0) {
if (wait_errno == EINTR) {
e.type = ET_SKIP;
return e;
}
stop_all(runp::result(runp::RS_JGF, "error code: WT4FAL")); // wait4 failed
}
if (run_program_config.need_show_trace_details) {
if (prev_pid != e.pid) {
cerr << "----------" << e.pid << "----------" << endl;
}
prev_pid = e.pid;
}
if (e.pid == rp_timer_pid) {
e.type = WIFEXITED(stat) || WIFSIGNALED(stat) ? ET_REAL_TLE : ET_SKIP;
return e;
}
if (has_real_TLE()) {
e.type = ET_REAL_TLE;
return e;
}
int p = rp_children_pos(e.pid);
if (p == -1) {
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "new_proc %lld\n", (long long int)e.pid);
}
rp_children_add(e.pid);
p = (int)rp_children.size() - 1;
}
e.cp = rp_children.data() + p;
ruse0p = p == 1 ? &ruse : NULL;
if (p >= 1) {
e.usertim = ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000;
e.usermem = ruse.ru_maxrss;
if (e.usertim > run_program_config.limits.time * 1000) {
e.type = ET_USER_CPU_TLE;
return e;
}
if (e.usermem > run_program_config.limits.memory * 1024) {
e.type = ET_MLE;
return e;
}
}
if (WIFEXITED(stat)) {
if (p == 0) {
stop_all(runp::result(runp::RS_JGF, "error code: ZROEX")); // the 0th child process exited unexpectedly
}
e.type = ET_EXIT;
e.exitcode = WEXITSTATUS(stat);
return e;
}
if (WIFSIGNALED(stat)) {
if (p == 0) {
stop_all(runp::result(runp::RS_JGF, "error code: ZROSIG")); // the 0th child process signaled unexpectedly
}
e.type = ET_SIGNALED;
e.sig = WTERMSIG(stat);
return e;
}
if (!WIFSTOPPED(stat)) {
stop_all(runp::result(runp::RS_JGF, "error code: NSTOP")); // expected WIFSTOPPED, but it is not
}
e.sig = WSTOPSIG(stat);
e.pevent = (unsigned)stat >> 16;
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "sig : %s\n", strsignal(e.sig));
}
if (e.cp->flags & CPF_STARTUP) {
int ptrace_opt = PTRACE_O_EXITKILL;
if (p == 0 || !run_program_config.unsafe) {
ptrace_opt |= PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK;
}
if (!run_program_config.unsafe) {
ptrace_opt |= PTRACE_O_TRACESECCOMP;
}
if (ptrace(PTRACE_SETOPTIONS, e.pid, NULL, ptrace_opt) == -1) {
stop_all(runp::result(runp::RS_JGF, "error code: PTRCFAL")); // ptrace failed
}
e.cp->flags &= ~CPF_STARTUP;
}
switch (e.sig) {
case SIGTRAP:
switch (e.pevent) {
case 0:
case PTRACE_EVENT_CLONE:
case PTRACE_EVENT_FORK:
case PTRACE_EVENT_VFORK:
e.sig = 0;
e.type = ET_RESTART;
return e;
case PTRACE_EVENT_SECCOMP:
e.sig = 0;
e.type = ET_SECCOMP_STOP;
return e;
default:
stop_all(runp::result(runp::RS_JGF, "error code: PTRCSIG")); // unknown ptrace signal
}
case SIGSTOP:
if (e.cp->flags & CPF_IGNORE_ONE_SIGSTOP) {
e.sig = 0;
e.type = ET_RESTART;
e.cp->flags &= ~CPF_IGNORE_ONE_SIGSTOP;
} else {
e.type = ET_SIGNAL_DELIVERY_STOP;
}
return e;
case SIGVTALRM:
// use rusage as the only standard for user CPU time TLE
// if the program reaches this line... then something goes wrong (rusage says no TLE, but timer says TLE)
// just ignore it and wait for another period
e.sig = 0;
e.type = ET_RESTART;
return e;
case SIGXFSZ:
e.type = ET_OLE;
return e;
default:
e.type = ET_SIGNAL_DELIVERY_STOP;
return e;
}
}
RunResult trace_children() {
void dispatch_event(run_event&& e) {
auto restart_op = PTRACE_CONT;
switch (e.type) {
case ET_SKIP:
return;
case ET_REAL_TLE:
stop_all(runp::result(runp::RS_TLE, "elapsed real time limit exceeded: >" + to_string(run_program_config.limits.real_time) + "s"));
case ET_USER_CPU_TLE:
stop_all(runp::result(runp::RS_TLE, "user CPU time limit exceeded: >" + to_string(run_program_config.limits.time) + "s"));
case ET_MLE:
stop_all(runp::result(runp::RS_MLE, "max RSS >" + to_string(run_program_config.limits.memory) + "MB"));
case ET_OLE:
stop_all(runp::result(runp::RS_OLE, "output limit exceeded: >" + to_string(run_program_config.limits.output) + "MB"));
case ET_EXIT:
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "exit : %d\n", e.exitcode);
}
if (rp_children[0].flags & CPF_STARTUP) {
stop_all(runp::result(runp::RS_JGF, "error code: CPCMDER1")); // rp_children mode error
} else if (rp_children.size() < 2 || (rp_children[1].flags & CPF_STARTUP)) {
stop_all(runp::result(runp::RS_JGF, "error code: CPCMDER2")); // rp_children mode error
} else {
if (e.cp == rp_children.data() + 1) {
stop_all(runp::result(runp::RS_AC, "exit with code " + to_string(e.exitcode), e.usertim, e.usermem, e.exitcode));
} else {
rp_children_del(e.pid);
}
}
return;
case ET_SIGNALED:
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "sig exit : %s\n", strsignal(e.sig));
}
if (e.cp == rp_children.data() + 1) {
stop_all(runp::result(runp::RS_RE, string("process terminated by signal: ") + strsignal(e.sig)));
} else {
rp_children_del(e.pid);
}
return;
case ET_SECCOMP_STOP:
if (e.cp != rp_children.data() + 0 && !run_program_config.unsafe) {
if (!e.cp->check_safe_syscall()) {
if (e.cp->suspicious) {
stop_all(runp::result(runp::RS_DGS, e.cp->error));
} else {
stop_all(runp::result(runp::RS_RE, e.cp->error));
}
}
if (e.cp->try_to_create_new_process) {
total_rp_children++;
if (total_rp_children > MAX_TOTAL_RP_CHILDREN) {
stop_all(runp::result(runp::RS_DGS, "the limit on the amount of child processes is exceeded"));
}
}
}
break;
case ET_SIGNAL_DELIVERY_STOP:
break;
case ET_RESTART:
break;
}
if (ptrace(restart_op, e.pid, NULL, e.sig) < 0) {
if (errno != ESRCH) {
stop_all(runp::result(runp::RS_JGF, "error code: PTRESFAL")); // ptrace restart failed
}
}
}
[[noreturn]] void trace_children() {
rp_timer_pid = fork();
if (rp_timer_pid == -1) {
stop_all();
return RunResult(RS_JGF);
runp::result(runp::RS_JGF, "error code: FKFAL2").dump_and_exit(); // fork failed
} else if (rp_timer_pid == 0) {
struct timespec ts;
ts.tv_sec = run_program_config.real_time_limit;
ts.tv_nsec = 0;
ts.tv_sec = run_program_config.limits.real_time;
ts.tv_nsec = 100 * 1000000;
nanosleep(&ts, NULL);
exit(0);
}
@ -388,185 +641,30 @@ RunResult trace_children() {
cerr << "timerpid " << rp_timer_pid << endl;
}
pid_t prev_pid = -1;
while (true) {
int stat = 0;
int sig = 0;
struct rusage ruse;
pid_t pid = wait4(-1, &stat, __WALL, &ruse);
if (run_program_config.need_show_trace_details) {
if (prev_pid != pid) {
cerr << "----------" << pid << "----------" << endl;
}
prev_pid = pid;
}
if (pid == rp_timer_pid) {
if (WIFEXITED(stat) || WIFSIGNALED(stat)) {
stop_all();
return RunResult(RS_TLE);
}
continue;
}
int p = rp_children_pos(pid);
if (p == -1) {
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "new_proc %lld\n", (long long int)pid);
}
if (rp_children_add(pid) == -1) {
stop_child(pid);
stop_all();
return RunResult(RS_DGS);
}
p = n_rp_children - 1;
}
int usertim = ruse.ru_utime.tv_sec * 1000 + ruse.ru_utime.tv_usec / 1000;
int usermem = ruse.ru_maxrss;
if (usertim > run_program_config.time_limit * 1000) {
stop_all();
return RunResult(RS_TLE);
}
if (usermem > run_program_config.memory_limit * 1024) {
stop_all();
return RunResult(RS_MLE);
}
if (WIFEXITED(stat)) {
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "exit : %d\n", WEXITSTATUS(stat));
}
if (rp_children[0].mode == -1) {
stop_all();
return RunResult(RS_JGF, -1, -1, WEXITSTATUS(stat));
} else {
if (pid == rp_children[0].pid) {
stop_all();
return RunResult(RS_AC, usertim, usermem, WEXITSTATUS(stat));
} else {
rp_children_del(pid);
continue;
}
}
}
if (WIFSIGNALED(stat)) {
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "sig exit : %d\n", WTERMSIG(stat));
}
if (pid == rp_children[0].pid) {
switch(WTERMSIG(stat)) {
case SIGXCPU: // nearly impossible
stop_all();
return RunResult(RS_TLE);
case SIGXFSZ:
stop_all();
return RunResult(RS_OLE);
default:
stop_all();
return RunResult(RS_RE);
}
} else {
rp_children_del(pid);
continue;
}
}
if (WIFSTOPPED(stat)) {
sig = WSTOPSIG(stat);
if (rp_children[p].mode == -1) {
if ((p == 0 && sig == SIGTRAP) || (p != 0 && sig == SIGSTOP)) {
if (p == 0) {
int ptrace_opt = PTRACE_O_EXITKILL | PTRACE_O_TRACESYSGOOD;
if (run_program_config.safe_mode) {
ptrace_opt |= PTRACE_O_TRACECLONE | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK;
ptrace_opt |= PTRACE_O_TRACEEXEC;
}
if (ptrace(PTRACE_SETOPTIONS, pid, NULL, ptrace_opt) == -1) {
stop_all();
return RunResult(RS_JGF);
}
}
sig = 0;
}
rp_children[p].mode = 0;
} else if (sig == (SIGTRAP | 0x80)) {
if (rp_children[p].mode == 0) {
if (run_program_config.safe_mode) {
if (!check_safe_syscall(pid, run_program_config.need_show_trace_details)) {
stop_all();
return RunResult(RS_DGS);
}
}
rp_children[p].mode = 1;
} else {
if (run_program_config.safe_mode) {
on_syscall_exit(pid, run_program_config.need_show_trace_details);
}
rp_children[p].mode = 0;
}
sig = 0;
} else if (sig == SIGTRAP) {
switch ((stat >> 16) & 0xffff) {
case PTRACE_EVENT_CLONE:
case PTRACE_EVENT_FORK:
case PTRACE_EVENT_VFORK:
sig = 0;
break;
case PTRACE_EVENT_EXEC:
rp_children[p].mode = 1;
sig = 0;
break;
case 0:
break;
default:
stop_all();
return RunResult(RS_JGF);
}
}
if (sig != 0) {
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "sig : %d\n", sig);
}
}
switch(sig) {
case SIGXCPU:
stop_all();
return RunResult(RS_TLE);
case SIGXFSZ:
stop_all();
return RunResult(RS_OLE);
}
}
ptrace(PTRACE_SYSCALL, pid, NULL, sig);
dispatch_event(next_event());
}
}
RunResult run_parent(pid_t pid) {
init_conf(run_program_config);
n_rp_children = 0;
rp_children_add(pid);
return trace_children();
}
int main(int argc, char **argv) {
self_path[readlink("/proc/self/exe", self_path, PATH_MAX)] = '\0';
parse_args(argc, argv);
try {
fs::path self_path = fs::read_symlink("/proc/self/exe");
runp::run_path = self_path.parent_path();
} catch (exception &e) {
runp::result(runp::RS_JGF, "error code: PTHFAL2").dump_and_exit(); // path failed
}
parse_args(argc, argv);
init_conf();
gettimeofday(&start_time, NULL);
pid_t pid = fork();
if (pid == -1) {
return put_result(run_program_config.result_file_name, RS_JGF);
runp::result(runp::RS_JGF, "error code: FKFAL2").dump_and_exit(); // fork failed
} else if (pid == 0) {
run_child();
} else {
return put_result(run_program_config.result_file_name, run_parent(pid));
rp_children_add(pid);
trace_children();
}
return put_result(run_program_config.result_file_name, RS_JGF);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,760 @@
#include <iostream>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <fstream>
#include <vector>
#include <set>
#include <algorithm>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <sys/user.h>
#include <sys/time.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <argp.h>
#include <seccomp.h>
#include "uoj_run.h"
enum EX_CHECK_TYPE : unsigned {
ECT_NONE = 0,
ECT_CNT = 1,
ECT_FILE_OP = 1 << 1, // it is a file operation
ECT_END_AT = 1 << 2, // this file operation ends with "at" (e.g., openat)
ECT_FILEAT_OP = ECT_FILE_OP | ECT_END_AT, // it is a file operation ended with "at"
ECT_FILE_W = 1 << 3, // intend to write
ECT_FILE_R = 1 << 4, // intend to read
ECT_FILE_S = 1 << 5, // intend to stat
ECT_CHECK_OPEN_FLAGS = 1 << 6, // check flags to determine whether it is to read/write (for open and openat)
ECT_FILE2_W = 1 << 7, // intend to write (2nd file)
ECT_FILE2_R = 1 << 8, // intend to read (2nd file)
ECT_FILE2_S = 1 << 9, // intend to stat (2nd file)
ECT_CLONE_THREAD = 1 << 10, // for clone(). Check that clone is making a non-suspicious thread
ECT_KILL_SIG0_ALLOWED = 1 << 11, // forbid kill but killing with sig0 is allowed
};
struct syscall_info {
EX_CHECK_TYPE extra_check;
int max_cnt;
bool should_soft_ban = false;
bool is_kill = false;
syscall_info()
: extra_check(ECT_CNT), max_cnt(0) {}
syscall_info(unsigned extra_check, int max_cnt)
: extra_check((EX_CHECK_TYPE)extra_check), max_cnt(max_cnt) {}
static syscall_info unlimited() {
return syscall_info(ECT_NONE, -1);
}
static syscall_info count_based(int max_cnt) {
return syscall_info(ECT_CNT, max_cnt);
}
static syscall_info with_extra_check(unsigned extra_check, int max_cnt = -1) {
if (max_cnt != -1) {
extra_check |= ECT_CNT;
}
return syscall_info(extra_check, max_cnt);
}
static syscall_info kill_type_syscall(unsigned extra_check = ECT_CNT, int max_cnt = 0) {
if (max_cnt != -1) {
extra_check |= ECT_CNT;
}
syscall_info res(extra_check, max_cnt);
res.is_kill = true;
return res;
}
static syscall_info soft_ban() {
syscall_info res(ECT_CNT, 0);
res.should_soft_ban = true;
return res;
}
};
#include "run_program_conf.h"
namespace fs = std::filesystem;
using namespace std;
typedef unsigned long long int reg_val_t;
#define REG_SYSCALL orig_rax
#define REG_RET rax
#define REG_ARG0 rdi
#define REG_ARG1 rsi
#define REG_ARG2 rdx
#define REG_ARG3 rcx
enum CHILD_PROC_FLAG : unsigned {
CPF_STARTUP = 1u << 0,
CPF_IGNORE_ONE_SIGSTOP = 1u << 2
};
struct rp_child_proc {
pid_t pid;
unsigned flags;
struct user_regs_struct reg = {};
int syscall = -1;
string error;
bool suspicious = false;
bool try_to_create_new_process = false;
void set_error_for_suspicious(const string &error);
void set_error_for_kill();
void soft_ban_syscall(int set_no);
bool check_safe_syscall();
bool check_file_permission(const string &op, const string &fn, char mode);
};
const size_t MAX_PATH_LEN = 512;
const uint64_t MAX_FD_ID = 1 << 20;
const string INVALID_PATH(PATH_MAX + 8, 'X');
const string EMPTY_PATH_AFTER_FD = "?empty_path_after_fd";
runp::config run_program_config;
set<string> writable_file_name_set;
set<string> readable_file_name_set;
set<string> statable_file_name_set;
set<string> soft_ban_file_name_set;
syscall_info syscall_info_set[N_SYSCALL];
pid_t get_tgid_from_pid(pid_t pid) {
ifstream fin("/proc/" + to_string(pid) + "/status");
string key;
while (fin >> key) {
if (key == "Tgid:") {
pid_t tgid;
if (fin >> tgid) {
return tgid;
} else {
return -1;
}
}
}
return -1;
}
bool is_len_valid_path(const string &path) {
return !path.empty() && path.size() <= MAX_PATH_LEN;
}
string path_or_len_invalid(const string &path) {
return is_len_valid_path(path) ? path : INVALID_PATH;
}
string basename(const string &path) {
if (!is_len_valid_path(path)) {
return INVALID_PATH;
}
size_t p = path.rfind('/');
if (p == string::npos) {
return path;
} else {
return path.substr(p + 1); // can be empty, e.g., path = "abc/"
}
}
string dirname(const string &path) {
if (!is_len_valid_path(path)) {
return INVALID_PATH;
}
size_t p = path.rfind('/');
if (p == string::npos) {
return INVALID_PATH;
} else {
return path.substr(0, p); // can be empty, e.g., path = "/abc"
}
}
string realpath(const string &path) {
if (!is_len_valid_path(path)) {
return INVALID_PATH;
}
static char real[PATH_MAX + 1] = {};
if (realpath(path.c_str(), real) == NULL) {
return INVALID_PATH;
}
return path_or_len_invalid(real);
}
string realpath_for_write(const string &path) {
string real = realpath(path);
if (!is_len_valid_path(path)) {
return INVALID_PATH;
}
string b = basename(path);
if (!is_len_valid_path(b) || b == "." || b == "..") {
return INVALID_PATH;
}
real = realpath(dirname(path));
if (!is_len_valid_path(real)) {
return INVALID_PATH;
}
return path_or_len_invalid(real + "/" + b);
}
string readlink(const string &path) {
if (!is_len_valid_path(path)) {
return INVALID_PATH;
}
static char buf[MAX_PATH_LEN + 1];
ssize_t n = readlink(path.c_str(), buf, MAX_PATH_LEN + 1);
if (n > (ssize_t)MAX_PATH_LEN) {
return INVALID_PATH;
} else {
buf[n] = '\0';
return path_or_len_invalid(buf);
}
}
string getcwd() {
char cwd[MAX_PATH_LEN + 1];
if (getcwd(cwd, MAX_PATH_LEN) == NULL) {
return INVALID_PATH;
} else {
return path_or_len_invalid(cwd);
}
}
string getcwdp(pid_t pid) {
return realpath("/proc/" + (pid == 0 ? "self" : to_string(pid)) + "/cwd");
}
string abspath(const string &path, pid_t pid, int fd = AT_FDCWD) {
static int depth = 0;
if (depth == 10 || !is_len_valid_path(path)) {
return INVALID_PATH;
}
vector<string> lv;
for (string cur = path; is_len_valid_path(cur); cur = dirname(cur)) {
lv.push_back(basename(cur));
}
reverse(lv.begin(), lv.end());
string pos;
if (path[0] == '/') {
pos = "/";
} else if (fd == AT_FDCWD) {
pos = getcwdp(pid);
} else {
depth++;
pos = abspath("/proc/self/fd/" + to_string(fd), pid);
depth--;
}
if (!is_len_valid_path(pos)) {
return INVALID_PATH;
}
struct stat stat_buf;
bool reachable = true;
for (auto &v : lv) {
if (reachable) {
if (lstat(pos.c_str(), &stat_buf) < 0 || !S_ISDIR(stat_buf.st_mode)) {
reachable = false;
}
}
if (reachable) {
if (v == ".") {
continue;
} else if (v == "..") {
pos = dirname(pos);
if (pos.empty()) {
pos = "/";
}
continue;
}
}
if (v.empty()) {
continue;
}
if (pos.back() != '/') {
pos += '/';
}
pos += v;
if (pos.size() > MAX_PATH_LEN) {
return INVALID_PATH;
}
if (reachable) {
string realpos;
if (pos == "/proc/self") {
realpos = "/proc/" + to_string(get_tgid_from_pid(pid));
} else if (pos == "/proc/thread-self") {
realpos = "/proc/" + to_string(get_tgid_from_pid(pid)) + "/" + to_string(pid);
} else {
if (lstat(pos.c_str(), &stat_buf) < 0) {
reachable = false;
continue;
}
if (!S_ISLNK(stat_buf.st_mode)) {
continue;
}
realpos = readlink(pos);
if (!is_len_valid_path(realpos)) {
return INVALID_PATH;
}
if (realpos[0] != '/') {
realpos = dirname(pos) + "/" + realpos;
}
}
depth++;
realpos = abspath(realpos, pid);
depth--;
if (!is_len_valid_path(realpos)) {
return INVALID_PATH;
}
pos = realpos;
}
}
return path_or_len_invalid(pos);
}
string getfdp(pid_t pid, int fd) {
if (fd == AT_FDCWD) {
return getcwdp(pid);
} else {
return abspath("/proc/self/fd/" + to_string(fd), pid);
}
}
inline bool is_in_set_smart(string name, const set<string> &s) {
if (name.size() > MAX_PATH_LEN) {
return false;
}
if (s.count(name)) {
return true;
}
int level;
for (level = 0; !name.empty(); name = dirname(name), level++) {
if (level == 1 && s.count(name + "/*")) {
return true;
}
if (s.count(name + "/")) {
return true;
}
}
if (level == 1 && s.count("/*")) {
return true;
}
if (s.count("/")) {
return true;
}
return false;
}
inline bool is_writable_file(string name) {
if (name == "/") {
return writable_file_name_set.count("system_root");
}
return is_in_set_smart(name, writable_file_name_set);
}
inline bool is_readable_file(const string &name) {
if (name == "/") {
return readable_file_name_set.count("system_root");
}
return is_in_set_smart(name, readable_file_name_set);
}
inline bool is_statable_file(const string &name) {
if (name == "/") {
return statable_file_name_set.count("system_root");
}
return is_in_set_smart(name, statable_file_name_set);
}
inline bool is_soft_ban_file(const string &name) {
if (name == "/") {
return soft_ban_file_name_set.count("system_root");
}
return is_in_set_smart(name, soft_ban_file_name_set);
}
void add_file_permission(const string &file_name, char mode) {
if (file_name.empty()) {
return;
}
if (mode == 'w') {
writable_file_name_set.insert(file_name);
} else if (mode == 'r') {
readable_file_name_set.insert(file_name);
} else if (mode == 's') {
statable_file_name_set.insert(file_name);
}
if (file_name == "system_root") {
return;
}
for (string name = dirname(file_name); !name.empty(); name = dirname(name)) {
statable_file_name_set.insert(name);
}
}
void init_conf() {
const runp::config &config = run_program_config;
add_file_permission(config.work_path, 'r');
add_file_permission(config.work_path + "/", 's');
if (folder_program_type_set.count(config.type)) {
add_file_permission(realpath(config.program_name) + "/", 'r');
} else {
add_file_permission(realpath(config.program_name), 'r');
}
vector<string> loads;
loads.push_back("default");
if (config.allow_proc) {
loads.push_back("allow_proc");
}
if (config.type != "default") {
loads.push_back(config.type);
}
for (string type : loads) {
if (allowed_syscall_list.count(type)) {
for (const auto &kv : allowed_syscall_list[type]) {
syscall_info_set[kv.first] = kv.second;
}
}
if (soft_ban_file_name_list.count(type)) {
for (const auto &name : soft_ban_file_name_list[type]) {
soft_ban_file_name_set.insert(name);
}
}
if (statable_file_name_list.count(type)) {
for (const auto &name : statable_file_name_list[type]) {
add_file_permission(name, 's');
}
}
if (readable_file_name_list.count(type)) {
for (const auto &name : readable_file_name_list[type]) {
add_file_permission(name, 'r');
}
}
if (writable_file_name_list.count(type)) {
for (const auto &name : writable_file_name_list[type]) {
add_file_permission(name, 'w');
}
}
}
for (const auto &name : config.readable_file_names) {
add_file_permission(name, 'r');
}
for (const auto &name : config.writable_file_names) {
add_file_permission(name, 'w');
}
if (config.type == "python2.7" || config.type == "python3") {
soft_ban_file_name_set.insert(dirname(realpath(config.program_name)) + "/__pycode__/");
} else if (config.type == "compiler") {
add_file_permission(config.work_path + "/", 'w');
}
readable_file_name_set.insert(writable_file_name_set.begin(), writable_file_name_set.end());
statable_file_name_set.insert(readable_file_name_set.begin(), readable_file_name_set.end());
}
string read_string_from_addr(reg_val_t addr, pid_t pid) {
int max_len = MAX_PATH_LEN + sizeof(reg_val_t);
char res[max_len + 1], *ptr = res;
while (ptr != res + max_len) {
*(reg_val_t*)ptr = ptrace(PTRACE_PEEKDATA, pid, addr, NULL);
for (size_t i = 0; i < sizeof(reg_val_t); i++, ptr++, addr++) {
if (*ptr == 0) {
return res;
}
}
}
res[max_len] = 0;
return res;
}
string read_abspath_from_addr(reg_val_t addr, pid_t pid) {
string p = read_string_from_addr(addr, pid);
string a = abspath(p, pid);
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "path : %s -> %s\n", p.c_str(), is_len_valid_path(a) ? a.c_str() : "INVALID!");
}
return a;
}
string read_abspath_from_fd_and_addr(reg_val_t fd, reg_val_t addr, pid_t pid) {
if (fd > MAX_FD_ID && (int)fd != AT_FDCWD) {
return INVALID_PATH;
}
string p = read_string_from_addr(addr, pid);
string a;
if (p.empty()) {
// this case is tricky
// if p is empty, in the following cases, Linux will understand the path as the path of fd:
// newfstatat + AT_EMPTY_PATH, linkat + AT_EMPTY_PATH, execveat + AT_EMPTY_PATH, readlinkat
// otherwise, the syscall will return with an error
// since fd is already opened, the program should have the permission to do the things listed above
// (no read -> write conversion, no deletion, no chmod, etc.)
// we just report this special case. the program will skip the permission check later
a = EMPTY_PATH_AFTER_FD;
} else {
a = abspath(p, pid, (int)fd);
}
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "path : %d, %s -> %s\n", (int)fd, p.c_str(), is_len_valid_path(a) ? a.c_str() : "INVALID!");
}
return a;
}
bool set_seccomp_bpf() {
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRACE(0));
if (!ctx) {
return false;
}
try {
for (int no : supported_soft_ban_errno_list) {
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(no), SYSCALL_SOFT_BAN_MASK | no, 0) < 0) {
throw system_error();
}
}
for (int i = 0; i < N_SYSCALL; i++) {
if (syscall_info_set[i].extra_check == ECT_NONE) {
if (syscall_info_set[i].should_soft_ban) {
if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), i, 0) < 0) {
throw system_error();
}
} else {
if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, i, 0) < 0) {
throw system_error();
}
}
}
}
seccomp_load(ctx);
} catch (system_error &e) {
seccomp_release(ctx);
return false;
}
seccomp_release(ctx);
return true;
}
void rp_child_proc::set_error_for_suspicious(const string &error) {
this->suspicious = true;
this->error = "suspicious system call invoked: " + error;
}
void rp_child_proc::set_error_for_kill() {
this->suspicious = false;
reg_val_t sig = this->syscall == __NR_tgkill ? this->reg.REG_ARG2 : this->reg.REG_ARG1;
this->error = "signal sent via " + syscall_name[this->syscall] + ": ";
if (sig != (unsigned)sig) {
this->error += "Unknown signal " + to_string(sig);
} else {
this->error += strsignal((int)sig);
}
}
void rp_child_proc::soft_ban_syscall(int set_no = EPERM) {
this->reg.REG_SYSCALL = SYSCALL_SOFT_BAN_MASK | set_no;
ptrace(PTRACE_SETREGS, pid, NULL, &this->reg);
}
bool rp_child_proc::check_file_permission(const string &op, const string &fn, char mode) {
string real_fn;
if (!fn.empty()) {
real_fn = mode == 'w' ? realpath_for_write(fn) : realpath(fn);
}
if (!is_len_valid_path(real_fn)) {
// path invalid or file not found
// ban this syscall softly
this->soft_ban_syscall(ENOENT);
return true;
}
string path_proc_self = "/proc/" + to_string(get_tgid_from_pid(this->pid));
if (real_fn.compare(0, path_proc_self.size() + 1, path_proc_self + "/") == 0) {
real_fn = "/proc/self" + real_fn.substr(path_proc_self.size());
} else if (real_fn == path_proc_self) {
real_fn = "/proc/self";
}
bool ok;
switch (mode) {
case 'w':
ok = is_writable_file(real_fn);
break;
case 'r':
ok = is_readable_file(real_fn);
break;
case 's':
ok = is_statable_file(real_fn);
break;
default:
ok = false;
break;
}
if (ok) {
return true;
}
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "check file permission %s : %s\n", op.c_str(), real_fn.c_str());
fprintf(stderr, "[readable]\n");
for (auto s: readable_file_name_set) {
cerr << s << endl;
}
fprintf(stderr, "[writable]\n");
for (auto s: writable_file_name_set) {
cerr << s << endl;
}
}
if (is_soft_ban_file(real_fn)) {
this->soft_ban_syscall(EACCES);
return true;
} else {
this->set_error_for_suspicious("intended to access a file without permission: " + op);
return false;
}
}
bool rp_child_proc::check_safe_syscall() {
ptrace(PTRACE_GETREGS, pid, NULL, &reg);
int cur_instruction = ptrace(PTRACE_PEEKTEXT, pid, reg.rip - 2, NULL) & 0xffff;
if (cur_instruction != 0x050f) {
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "informal syscall %d\n", cur_instruction);
}
this->set_error_for_suspicious("incorrect opcode " + to_string(cur_instruction));
return false;
}
if (0 > (long long int)reg.REG_SYSCALL || (long long int)reg.REG_SYSCALL >= N_SYSCALL) {
this->set_error_for_suspicious(to_string(reg.REG_SYSCALL));
return false;
}
syscall = (int)reg.REG_SYSCALL;
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "[syscall %s]\n", syscall_name[syscall].c_str());
}
this->try_to_create_new_process = syscall == __NR_fork || syscall == __NR_clone || syscall == __NR_clone3 || syscall == __NR_vfork;
auto &cursc = syscall_info_set[syscall];
if (cursc.extra_check & ECT_CNT) {
if (cursc.max_cnt == 0) {
if (cursc.should_soft_ban) {
this->soft_ban_syscall();
return true;
} else {
if (cursc.is_kill) {
this->set_error_for_kill();
} else {
this->set_error_for_suspicious(syscall_name[syscall]);
}
return false;
}
}
cursc.max_cnt--;
}
if (cursc.extra_check & ECT_KILL_SIG0_ALLOWED) {
reg_val_t sig = this->syscall == __NR_tgkill ? this->reg.REG_ARG2 : this->reg.REG_ARG1;
if (sig != 0) {
this->set_error_for_kill();
return false;
}
}
if (cursc.extra_check & ECT_FILE_OP) {
string fn;
if (cursc.extra_check & ECT_END_AT) {
fn = read_abspath_from_fd_and_addr(reg.REG_ARG0, reg.REG_ARG1, pid);
} else {
fn = read_abspath_from_addr(reg.REG_ARG0, pid);
}
string textop = syscall_name[syscall];
char mode = 'w';
if (cursc.extra_check & ECT_CHECK_OPEN_FLAGS) {
reg_val_t flags = cursc.extra_check & ECT_END_AT ? reg.REG_ARG2 : reg.REG_ARG1;
switch (flags & O_ACCMODE) {
case O_RDONLY:
if ((flags & O_CREAT) == 0 && (flags & O_EXCL) == 0 && (flags & O_TRUNC) == 0) {
textop += " (for read)";
mode = 'r';
} else {
textop += " (for read & write)";
}
break;
case O_WRONLY:
textop += " (for write)";
break;
case O_RDWR:
textop += " (for read & write)";
break;
default:
textop += " (with invalid flags)";
break;
}
} else if (cursc.extra_check & ECT_FILE_S) {
mode = 's';
} else if (cursc.extra_check & ECT_FILE_R) {
mode = 'r';
} else if (cursc.extra_check & ECT_FILE_W) {
mode = 'w';
} // else, error!
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "%-8s : %s\n", syscall_name[syscall].c_str(), fn.c_str());
}
if (fn != EMPTY_PATH_AFTER_FD && !check_file_permission(textop, fn, mode)) {
return false;
}
if (cursc.extra_check & ECT_FILE2_S) {
mode = 's';
} else if (cursc.extra_check & ECT_FILE2_R) {
mode = 'r';
} else if (cursc.extra_check & ECT_FILE2_W) {
mode = 'w';
} else {
mode = '?';
}
if (mode != '?') {
if (cursc.extra_check & ECT_END_AT) {
fn = read_abspath_from_fd_and_addr(reg.REG_ARG2, reg.REG_ARG3, pid);
} else {
fn = read_abspath_from_addr(reg.REG_ARG1, pid);
}
if (run_program_config.need_show_trace_details) {
fprintf(stderr, "%-8s : %s\n", syscall_name[syscall].c_str(), fn.c_str());
}
if (fn != EMPTY_PATH_AFTER_FD && !check_file_permission(textop, fn, mode)) {
return false;
}
}
}
if (cursc.extra_check & ECT_CLONE_THREAD) {
reg_val_t flags = reg.REG_ARG0;
if (!(flags & CLONE_THREAD)) {
this->set_error_for_suspicious("intended to create a new process");
return false;
}
auto standard_flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND;
standard_flags |= CLONE_SYSVSEM | CLONE_SETTLS |CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
if (!(flags & standard_flags)) {
this->set_error_for_suspicious("intended to create a non-standard thread");
return false;
}
}
return true;
}

View File

@ -1,12 +1,13 @@
FROM ubuntu:20.04
FROM ubuntu:22.04
ARG CLONE_ADDFLAG
ENV DEBIAN_FRONTEND=noninteractive
RUN dpkg -s gnupg 2>/dev/null || (apt-get update && apt-get install -y gnupg) &&\
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF &&\
apt-get update && apt-get install -y git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile libapache2-mod-php php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl g++ make re2c libv8-7.5-dev libyaml-dev &&\
yes | pecl install yaml &&\
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1A10946ED858A0DF &&\
echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu jammy main" | tee /etc/apt/sources.list.d/ondrej-php.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4F4EA0AAE5267A6C &&\
apt-get update --allow-unauthenticated &&\
apt-get install -y --allow-unauthenticated -o Dpkg::Options::="--force-overwrite" libv8 php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd libseccomp-dev git vim ntp zip unzip curl wget libapache2-mod-xsendfile mysql-server php-pear cmake fp-compiler re2c libv8-7.5-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk &&\
git clone https://github.com/phpv8/v8js.git --depth=1 -b 2.1.2 /tmp/pear/download/v8js-master && cd /tmp/pear/download/v8js-master &&\
phpize && ./configure --with-php-config=/usr/bin/php-config --with-v8js=/opt/libv8-7.5 && make install && cd -

View File

@ -18,8 +18,10 @@ getAptPackage(){
# Update apt sources and install
export DEBIAN_FRONTEND=noninteractive
dpkg -s gnupg 2>/dev/null || (apt-get update && apt-get install -y gnupg)
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D858A0DF
apt-get update && apt-get install -y git vim ntp zip unzip curl wget apache2 libapache2-mod-xsendfile libapache2-mod-php php php-dev php-pear php-zip php-mysql php-mbstring php-gd php-intl php-xsl g++ make re2c libv8-7.5-dev libyaml-dev
echo "deb http://ppa.launchpad.net/stesie/libv8/ubuntu bionic main" | tee /etc/apt/sources.list.d/stesie-libv8.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1A10946ED858A0DF
echo "deb http://ppa.launchpad.net/ondrej/php/ubuntu jammy main" | tee /etc/apt/sources.list.d/ondrej-php.list && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4F4EA0AAE5267A6C
apt-get update --allow-unauthenticated
apt-get install -y --allow-unauthenticated -o Dpkg::Options::="--force-overwrite" libv8 php7.4 php7.4-yaml php7.4-xml php7.4-dev php7.4-zip php7.4-mysql php7.4-mbstring php7.4-gd libseccomp-dev git vim ntp zip unzip curl wget libapache2-mod-xsendfile mysql-server php-pear cmake fp-compiler re2c libv8-7.5-dev libyaml-dev python2.7 python3.10 python3-requests openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk
# Install PHP extensions
yes | pecl install yaml
git clone https://github.com/phpv8/v8js.git --depth=1 -b 4c026f3fb291797c109adcabda6aeba6491fe44f /tmp/pear/download/v8js-master && cd /tmp/pear/download/v8js-master
@ -77,7 +79,7 @@ UOJEOF
#define UOJ_JUDGER_PYTHON3_VERSION "3.6"
#define UOJ_JUDGER_FPC_VERSION "3.0.4"
UOJEOF
make runner -j$(($(nproc) + 1)) && cd /opt/uoj/web
make all -j$(($(nproc) + 1)) && cd /opt/uoj/web
}
dockerInitProgress() {