diff options
author | LAN-TW <lantw44@gmail.com> | 2013-02-20 09:47:22 +0800 |
---|---|---|
committer | LAN-TW <lantw44@gmail.com> | 2013-02-20 09:53:17 +0800 |
commit | 0eefe53e15fddb6a9131222e3d389e73de3c7013 (patch) | |
tree | 37e1f21632b7dbe7849e70b3084cd40c5921ea1b | |
parent | 2a6d9c778a19a55d8d91c119749d9339165a382f (diff) | |
download | sctjudge-master.tar.gz sctjudge-master.tar.zst sctjudge-master.zip |
關於程式架構,請參考 README 檔案。
目前仍許多功能在舊版已實作,但此版本尚未加入。
-rw-r--r-- | .gitignore | 9 | ||||
-rw-r--r-- | INSTALL | 7 | ||||
-rw-r--r-- | README | 20 | ||||
-rw-r--r-- | configure.ac | 14 | ||||
-rw-r--r-- | src/CliCommon.h | 18 | ||||
-rw-r--r-- | src/CliMain.c | 74 | ||||
-rw-r--r-- | src/JudgeCommon.h | 48 | ||||
-rw-r--r-- | src/JudgeMain.c | 85 | ||||
-rw-r--r-- | src/Makefile.am | 7 | ||||
-rw-r--r-- | src/ProcCheckTime.c (renamed from src/checktle.c) | 53 | ||||
-rw-r--r-- | src/ProcCommon.h | 121 | ||||
-rw-r--r-- | src/ProcMain.c | 1634 | ||||
-rw-r--r-- | src/ProcMonitor.c (renamed from src/disptime.c) | 15 | ||||
-rw-r--r-- | src/SctCommon.c (renamed from src/common.c) | 80 | ||||
-rw-r--r-- | src/SctCommon.h | 88 | ||||
-rw-r--r-- | src/SctConst.h | 11 | ||||
-rw-r--r-- | src/SctMain.c | 645 | ||||
-rw-r--r-- | src/SctVersion.h.in (renamed from src/version.h.in) | 0 | ||||
-rw-r--r-- | src/common.h | 24 | ||||
-rw-r--r-- | src/config2.h | 14 | ||||
-rw-r--r-- | src/main.c | 456 | ||||
-rw-r--r-- | src/mkchild.c | 711 | ||||
-rw-r--r-- | src/sctcore.h | 90 |
23 files changed, 2879 insertions, 1345 deletions
@@ -8,6 +8,7 @@ *.so *.gz *.swp +tags aclocal.m4 autom4te.cache autoscan.log @@ -22,14 +23,16 @@ configure.scan depcomp missing install-sh +/.cproject +/.project /Makefile /Makefile.in /sctjudge.spec /src/Makefile /src/Makefile.in -/src/config.h -/src/config.h.in -/src/version.h +/src/SctConfig.h +/src/SctConfig.h.in +/src/SctVersion.h /src/stamp-h1 /src/sctjudge /l4basic/l4arg/Makefile @@ -18,9 +18,4 @@ ============================================================================= -以下列出 configure 而可用的選項(也可用 ./configure --help 查到) - --enable-debug 停用 -O2 選項並加上 -g (但如果你有指定 CFLAGS 就沒效了) - --enable-static 靜態連結可執行檔 - --enable-procmon 使用 Linux 下 /proc 提供的資訊監控程序 - --enable-cap 使用 Linux capability 支援(而不使用 setuid) - +可用的選項,請參考 ./configure --help 的輸出! @@ -11,8 +11,26 @@ Simple Common Test Judge * Linux 下的程序監視器 (0.8) * 判斷 PE 的能力 * 使用 pipe 取得輸出,而不依賴輸出檔 - * 較易操作的使用者界面,例如 TUI,但不需要 GUI + * 較易操作的使用者界面,例如 TUI 和 GUI 使用注意事項:因為受測程式在執行時,式無法開啟任何額外函式庫檔案的。因此編譯受 測程式時,請加上 -static 來編譯成可獨立執行的可執行檔,否則無法得到正確的結果! (可能需要安裝額外的套件才能編譯成靜態的執行檔,例如說 glibc-static 這類的套件) + +============================================================================= + +程式(預計)命名規則: + * 檔名 Sct 開頭 -> 通用函式,沒有特定命名規則 + * 檔名 Judge 開頭 -> 最重要的程式,與檢測相關,函式以 sctjudge_ 開頭 + * 檔名 Proc 開頭 -> 與子程序相關,函式以 sctproc_ 開頭 + * 含有 Main 的檔案或函式,表示該類型函式的進入點 + * 目前計畫中的 UI 有 Cli、Tui、Gtk 三種,以後看能不能再加 + +程式(預計)架構: + Sct -> UI -> Judge -> Proc + +函式呼叫順序: + main -> UI -> sctjudge_main -> sctproc_main + +基本原則: + 下層的函式不需要與不會知道上層的資料,上層的函式可以會需要下層的資料 diff --git a/configure.ac b/configure.ac index 3309e70..12f6e77 100644 --- a/configure.ac +++ b/configure.ac @@ -4,8 +4,8 @@ AC_INIT([sctjudge], [1.0], [lantw44@gmail.com]) AM_INIT_AUTOMAKE([foreign -Wall]) AM_SILENT_RULES([yes]) -AC_CONFIG_SRCDIR([src/main.c]) -AC_CONFIG_HEADERS([src/config.h]) +AC_CONFIG_SRCDIR([src/SctMain.c]) +AC_CONFIG_HEADERS([src/SctConfig.h]) AC_CONFIG_FILES([ Makefile sctjudge.spec @@ -13,7 +13,7 @@ AC_CONFIG_FILES([ l4basic/l4bds/Makefile l4basic/l4darr/Makefile src/Makefile - src/version.h + src/SctVersion.h ]) releasedate="2013-01-27" @@ -222,6 +222,14 @@ if test x"${opt_procmon}" = xyes; then ;; esac fi + +case "${opt_procmon}" in + linux|freebsd|yes|no|guess) + ;; + *) + AC_MSG_ERROR([--enable-procmon="${opt_procmon}" is invalid]) + ;; +esac if test x"${opt_procmon}" = xlinux || test x"${opt_procmon}" = xguess; then diff --git a/src/CliCommon.h b/src/CliCommon.h new file mode 100644 index 0000000..27e0502 --- /dev/null +++ b/src/CliCommon.h @@ -0,0 +1,18 @@ +#ifndef SCTJUDGE_CLI_COMMON +#define SCTJUDGE_CLI_COMMON + +#include "ProcCommon.h" +#include "JudgeCommon.h" + +#include <stdarg.h> + +/* --- CliMain.c --- */ +void sctcli_main(void*, JUDGEINFO*); +void sctcli_logger(int, const char*, ...); +void sctcli_err(const char*, ...); +void sctcli_normal_backend(void*, const char*, va_list); +void sctcli_debug_backend(void*, const char*, va_list); +void sctcli_err_backend(void*, const char*, va_list); +void sctcli_monitor(void*, const PROCMONINFO*); + +#endif diff --git a/src/CliMain.c b/src/CliMain.c new file mode 100644 index 0000000..050d947 --- /dev/null +++ b/src/CliMain.c @@ -0,0 +1,74 @@ +#ifdef HAVE_CONFIG_H +# include "SctConfig.h" +#endif + +#include "SctVersion.h" +#include "ProcCommon.h" +#include "JudgeCommon.h" +#include "CliCommon.h" + +#include <stdio.h> +#include <stdarg.h> + +static int sctcli_verbose; + +/* TODO: 實作 configfile 的功能 */ +void sctcli_main(void* configfile, JUDGEINFO* sjdata){ + sctcli_verbose = sjdata->procinfo.i_verbose; + sjdata->procinfo.i_func_arg = NULL; + sjdata->procinfo.f_debug = sctcli_debug_backend; + sjdata->procinfo.f_normal = sctcli_normal_backend; + sjdata->procinfo.f_err = sctcli_err_backend; + sjdata->procinfo.f_monitor = sctcli_monitor; + if(configfile == NULL){ + sctcli_logger(1, "開始執行檢測工作......\n"); + sctcli_logger(2, "%s: Call sctjudge_main() [Judge function]\n", + __func__); + if(sctjudge_main(sjdata) < 0){ /* 執行檢測工作 */ + sctcli_err(NULL, "檢測工作失敗"); + return; + } + + }else{ + sctcli_err(NULL, "抱歉,設定檔功能尚未實作\n"); + return; + } +} + +void sctcli_logger(int level, const char* format, ...){ + va_list ap; + va_start(ap, format); + if(sctcli_verbose >= level){ + if(level >= 2){ + sctcli_debug_backend(NULL, format, ap); + }else{ + sctcli_normal_backend(NULL, format, ap); + } + } + va_end(ap); +} + +void sctcli_normal_backend(void* data, const char* format, va_list varlist){ + vfprintf(stdout, format, varlist); +} + +void sctcli_debug_backend(void* data, const char* format, va_list varlist){ + fputs("DEBUG: ", stdout); + vfprintf(stdout, format, varlist); +} + +void sctcli_err_backend(void* data, const char* format, va_list varlist){ + fputs("錯誤: ", stderr); + vfprintf(stderr, format, varlist); +} + +void sctcli_err(const char* format, ...){ + va_list ap; + va_start(ap, format); + sctcli_err_backend(NULL, format, ap); + va_end(ap); +} + +void sctcli_monitor(void* data, const PROCMONINFO* moninfo){ + return; +} diff --git a/src/JudgeCommon.h b/src/JudgeCommon.h new file mode 100644 index 0000000..19b4eef --- /dev/null +++ b/src/JudgeCommon.h @@ -0,0 +1,48 @@ +#ifndef SCTJUDGE_JUDGE_COMMON +#define SCTJUDGE_JUDGE_COMMON + +#include "ProcCommon.h" + +/* 這裡的東西記得和 ProcCommon.h 與 SctMain.c 連動 */ +#define JUDGEINFO_RESULT_OK PROCINFO_RESULT_OK +#define JUDGEINFO_RESULT_RE PROCINFO_RESULT_RE +#define JUDGEINFO_RESULT_TLE PROCINFO_RESULT_TLE +#define JUDGEINFO_RESULT_OLE PROCINFO_RESULT_OLE +#define JUDGEINFO_RESULT_SLE PROCINFO_RESULT_SLE +#define JUDGEINFO_RESULT_AB PROCINFO_RESULT_AB +#define JUDGEINFO_RESULT_UD PROCINFO_RESULT_UD +#define JUDGEINFO_RESULT_WA (PROCINFO_RESULT_MAX + 0) +#define JUDGEINFO_RESULT_PE (PROCINFO_RESULT_MAX + 1) +#define JUDGEINFO_RESULT_AC (PROCINFO_RESULT_MAX + 2) +#define JUDGEINFO_RESULT_MAX (PROCINFO_RESULT_MAX + 3) + +/* --- JudgeMain.c --- */ + +/* 全域常數,表示 JUDGEINFO 中 result 的值 */ +extern const char* sctjudge_result_text[][2]; +#define JUDGEINFO_GET_RESULT_ABBR(n) (sctjudge_result_text[(n)][0]) +#define JUDGEINFO_GET_RESULT_TEXT(n) (sctjudge_result_text[(n)][1]) + +typedef struct sctjudge_judgeinfo{ + PROCINFO procinfo; /* 交給 Proc 系列函式的參數和結果 */ + int procmain; /* ProcMain 回傳的東西 */ + int flag; /* 與 Judge 相關的參數,但不會傳到 Proc */ + int pedetect; /* 偵測 PE 的方法 */ + int result; /* Judge 的結果 */ + /* 未完待續...... (special judge ?) */ +} JUDGEINFO; + +/* JUDGEINFO 裡面 judgeflag 的值 */ +#define JUDGEINFO_FLAG_COPY 0x00000001 /* chroot 所以要複製執行檔 */ + +/* sctjudge_main 的回傳值,0 表示正常 */ +#define SCTJUDGE_JUDGE_EXIT_SUCCESS 0 +#define SCTJUDGE_JUDGE_EXIT_MAX (SCTJUDGE_PROC_EXIT_MAX + 0) + +int sctjudge_main(JUDGEINFO*); +void sctjudge_setdefault(JUDGEINFO*); +void sctjudge_freestring(JUDGEINFO*); +void sctjudge_logger(int, const char*, ...); +void sctjudge_err(const char*, ...); + +#endif diff --git a/src/JudgeMain.c b/src/JudgeMain.c new file mode 100644 index 0000000..5d42c8a --- /dev/null +++ b/src/JudgeMain.c @@ -0,0 +1,85 @@ +#ifdef HAVE_CONFIG_H +# include "SctConfig.h" +#endif + +#include "SctCommon.h" +#include "SctVersion.h" +#include "JudgeCommon.h" +#include <stdarg.h> + +const char* sctjudge_result_text[JUDGEINFO_RESULT_MAX][2] = { + {"OK" , "正常結束"}, + {"RE" , "執行過程中發生錯誤"}, + {"TLE", "超過時間限制"}, + {"OLE", "輸出超過限制"}, + {"SLE", "暫停次數太多"}, + {"AB" , "使用者中斷操作"}, + {"UD" , "未定義的結果"}, + {"WA" , "錯誤的答案"}, + {"PE" , "程式大致正確但輸出格式不符"}, + {"AC" , "已通過檢測"} +}; + +static void (*sctjudge_output_debug)(void*, const char*, va_list); +static void (*sctjudge_output_normal)(void*, const char*, va_list); +static void (*sctjudge_output_err)(void*, const char*, va_list); +static void* sctjudge_output_arg; +static int sctjudge_verbose; + +int sctjudge_main(JUDGEINFO* judgeinfo){ + sctjudge_output_debug = judgeinfo->procinfo.f_debug; + sctjudge_output_normal = judgeinfo->procinfo.f_normal; + sctjudge_output_err = judgeinfo->procinfo.f_err; + sctjudge_output_arg = judgeinfo->procinfo.i_func_arg; + sctjudge_verbose = judgeinfo->procinfo.i_verbose; + + /* 列出設定值,僅供偵錯使用 */ + sctjudge_logger(2, "%s: 列出 JUDGEINFO 中的設定值\n", __func__); + sctjudge_logger(2, "%s: flag: COPY [自動複製可執行檔]: %s\n", __func__, + DISPLAY_YESNO(judgeinfo->flag, JUDGEINFO_FLAG_COPY)); + sctjudge_logger(2, "%s: petect: 此功能尚未實作\n", __func__); + + int rval; + + /* 準備執行程式 */ + sctjudge_logger(1, "開始執行受測程式......\n"); + sctjudge_logger(2, "%s: Call sctproc_main() [Process creator]\n", __func__); + if((rval = sctproc_main(&(judgeinfo->procinfo)))){ + sctjudge_err("受測程式執行失敗\n"); + return rval; + } + return 0; +} + +void sctjudge_setdefault(JUDGEINFO* judgeinfo){ + sctproc_setdefault(&(judgeinfo->procinfo)); + judgeinfo->procmain = 0; + judgeinfo->pedetect = 0; + judgeinfo->result = 0; + judgeinfo->flag = + JUDGEINFO_FLAG_COPY; +} + +void sctjudge_freestring(JUDGEINFO* judgeinfo){ + sctproc_freestring(&(judgeinfo->procinfo)); +} + +void sctjudge_logger(int level, const char* format, ...){ + va_list ap; + va_start(ap, format); + if(sctjudge_verbose >= level){ + if(level >= 2){ + (*sctjudge_output_debug)(sctjudge_output_arg, format, ap); + }else{ + (*sctjudge_output_normal)(sctjudge_output_arg, format, ap); + } + } + va_end(ap); +} + +void sctjudge_err(const char* format, ...){ + va_list ap; + va_start(ap, format); + (*sctjudge_output_err)(sctjudge_output_arg, format, ap); + va_end(ap); +} diff --git a/src/Makefile.am b/src/Makefile.am index b85889a..1c95d9c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,8 +3,11 @@ sctjudge_CFLAGS = -Wall -pipe -pthread -I../l4basic/l4darr -I../l4basic/l4arg\ -D_REENTRANT -D_THREAD_SAFE sctjudge_LDFLAGS = -L../l4basic/l4darr -L../l4basic/l4arg sctjudge_LDADD = -ll4arg -ll4darr -sctjudge_SOURCES = common.c common.h config.h sctcore.h disptime.c checktle.c\ - main.c mkchild.c version.h config2.h +sctjudge_SOURCES = SctMain.c SctCommon.c SctCommon.h SctVersion.h \ + ProcMain.c ProcCommon.h ProcCheckTime.c ProcMonitor.c \ + JudgeMain.c JudgeCommon.h \ + CliMain.c CliCommon.h \ + SctConfig.h SctConst.h if STATIC_EXEC sctjudge_LDFLAGS += -static diff --git a/src/checktle.c b/src/ProcCheckTime.c index 126a5e3..9efbe05 100644 --- a/src/checktle.c +++ b/src/ProcCheckTime.c @@ -1,10 +1,10 @@ #ifdef HAVE_CONFIG_H -# include "config.h" +# include "SctConfig.h" #endif -#include "config2.h" -#include "common.h" -#include "sctcore.h" +#include "SctConst.h" +#include "SctCommon.h" +#include "ProcCommon.h" #include <string.h> #include <unistd.h> @@ -13,40 +13,35 @@ #include <pthread.h> #include <semaphore.h> -volatile sig_atomic_t break_flag; +volatile sig_atomic_t sctproc_abort; static void break_handler(int signo){ - break_flag = 1; - sem_post(&tlethr); + sctproc_abort = 1; + sem_post(&checktime_sem); } -void* sctjudge_checktle(void* arg){ - pid_t pidcopy; - long long sleeptime = (long long)(*(int*)arg) * 1000000; +void* sctproc_checktime(void* arg){ + PROCINFO* procinfo = arg; struct sigaction break_catch; struct timespec timelimit, timeinit, timecur, timeexpire; + long long sleeptime = (long long)(procinfo->i_limit_time) * 1000000; - pthread_mutex_lock(&tkill_mx); - tkill_yes = 1; - pthread_mutex_unlock(&tkill_mx); + pthread_mutex_lock(&checktime_mutex); + checktime_exist = 1; + pthread_mutex_unlock(&checktime_mutex); - sem_wait(&tlethr); clock_gettime(CLOCK_REALTIME, &timeinit); #ifndef HAVE_CONF_CAP enable_setuid(); #endif - break_flag = 0; + sctproc_abort = 0; memset(&break_catch, 0, sizeof(break_catch)); break_catch.sa_handler = &break_handler; sigaction(SIGINT, &break_catch, NULL); sigaction(SIGTERM, &break_catch, NULL); - pthread_mutex_lock(&pidmutex); - pidcopy = pidchild; - pthread_mutex_unlock(&pidmutex); - timelimit.tv_sec = timeinit.tv_sec + sleeptime / 1000000000; timelimit.tv_nsec = timeinit.tv_nsec + sleeptime % 1000000000; checktimespec(&timelimit); @@ -54,22 +49,22 @@ void* sctjudge_checktle(void* arg){ do{ clock_gettime(CLOCK_REALTIME, &timecur); timeexpire.tv_sec = timecur.tv_sec; - timeexpire.tv_nsec = timecur.tv_nsec + SCT_CHECKTLE_INTERVAL; + timeexpire.tv_nsec = timecur.tv_nsec + SCTJUDGE_PROC_CHECKTIME_INTERVAL; checktimespec(&timeexpire); }while(comparetimespec(&timecur, &timelimit) < 0 && - sem_timedwait(&tlethr, &timeexpire)); + sem_timedwait(&checktime_sem, &timeexpire)); - if(!break_flag){ - pthread_mutex_lock(&judge_tle_mx); - judge_tle = 1; - pthread_mutex_unlock(&judge_tle_mx); + if(!sctproc_abort){ + pthread_mutex_lock(&sctproc_tle_mutex); + sctproc_tle = 1; + pthread_mutex_unlock(&sctproc_tle_mutex); } - kill(pidcopy, SIGKILL); + kill(child_pid, SIGKILL); - pthread_mutex_lock(&tkill_mx); - tkill_yes = 0; - pthread_mutex_unlock(&tkill_mx); + pthread_mutex_lock(&checktime_mutex); + checktime_exist = 0; + pthread_mutex_unlock(&checktime_mutex); return NULL; } diff --git a/src/ProcCommon.h b/src/ProcCommon.h new file mode 100644 index 0000000..19aa250 --- /dev/null +++ b/src/ProcCommon.h @@ -0,0 +1,121 @@ +#ifndef SCTJUDGE_PROC_COMMON +#define SCTJUDGE_PROC_COMMON + +#include <stdarg.h> +#include <signal.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <pthread.h> +#include <semaphore.h> + +#include <l4darr.h> + +/* --- ProcCheckTime.c --- */ +extern volatile sig_atomic_t sctproc_abort; +void* sctproc_checktime(void*); + +/* --- ProcMonitor.c --- */ +void* sctproc_monitor(void*); +typedef struct sctjudge_procmoninfo{ + unsigned long time; + unsigned long cpuuser; + unsigned long cpusys; + unsigned long vm; +} PROCMONINFO; + +/* --- ProcMain.c --- */ + +/* 子程序 PID 和對應的 mutex */ +extern pid_t child_pid; +extern pthread_mutex_t child_mutex; + +/* 兩個 thread 的資料 */ +extern pthread_t checktime_thread, monitor_thread; +extern _Bool checktime_exist, monitor_exist; +extern pthread_mutex_t checktime_mutex, monitor_mutex; + +/* 用來讓另外兩個 thread 卡住的 semaphore */ +extern sem_t checktime_sem, monitor_sem; + +/* 判斷有無 TLE,此變數由 sctjudge_checktle 設定 */ +extern _Bool sctproc_tle; +extern pthread_mutex_t sctproc_tle_mutex; + +/* 同時只能有一個函式執行輸出 */ +extern pthread_mutex_t output_mutex; + +/* ProcMain 作為參數和回傳的 struct,i 表示輸入,o 表示輸出 */ +typedef struct sctjudge_procinfo{ + int i_verbose; /* quiet=0, normal=1, debug=2 */ + char* i_exec; /* 輸入:可執行檔的路徑 */ + L4DA* i_argv; /* 輸入:執行時的 argv,會取出 char** 指標 */ + char* i_chroot; /* 輸入:要作為 chroot 的目錄 */ + char* i_infile; /* 輸入:要作為 stdin 的檔案 */ + char* i_outfile; /* 輸入:要作為 stdout 的檔案 */ + char* i_errfile; /* 輸入:要作為 stderr 的檔案 */ + uid_t i_uid; /* 輸入:要切換成哪個使用者 */ + gid_t i_gid; /* 輸入:要切換成哪個群組 */ + unsigned i_flag; /* 輸入:額外的設定值 */ + unsigned i_limit_time; /* 輸入:執行時間限制 */ + unsigned i_limit_mem; /* 輸入:虛擬記憶體上限 (RLIMIT_AS) */ + unsigned i_limit_core; /* 輸入:core 檔案大小上限 (RLIMIT_CORE) */ + unsigned i_limit_cpu; /* 輸入:CPU 時間上限 (RLIMIT_CPU) */ + unsigned i_limit_out; /* 輸入:檔案大小上限 (RLIMIT_FSIZE) */ + unsigned i_limit_file; /* 輸入:檔案開啟數量上限 (RLIMIT_NOFILE) */ + unsigned i_limit_proc; /* 輸入:process 數量上限 (RLIMIT_NPROC) */ + unsigned i_limit_stack; /* 輸入:stack 大小上限 (RLIMIT_STACK) */ + void* i_func_arg; /* 輸入:送入 f_ 系列函式的第一個參數 */ + int o_result; /* 輸出:檢測結果 */ + int o_procexit; /* 輸出:child process 結束的回傳值 */ + int o_signal; /* 輸出:受測程式因哪個 signal 結束 */ + struct timespec o_runtime; /* 輸出:受測程式執行時間 */ + struct rusage o_rusage; /* 輸出:child process 的 rusage */ + void (*f_debug) (void*, const char*, va_list); + void (*f_normal) (void*, const char*, va_list); + void (*f_err) (void*, const char*, va_list); + void (*f_monitor) (void*, const PROCMONINFO*); +} PROCINFO; + +/* PROCINFO 裡面 i_flag 的值 */ +#define PROCINFO_FLAG_FORCE 0x00000001 /* 強制執行 */ +#define PROCINFO_FLAG_DRYRUN 0x00000002 /* 只檢查設定而不執行 */ +#define PROCINFO_FLAG_SECURE_CHECK 0x00000004 /* 檢查所有「建議的」必要選項 */ +#define PROCINFO_FLAG_REDIR_STDERR 0x00000008 /* stderr 也導入輸出檔 */ +#define PROCINFO_FLAG_SETUID 0x00000010 /* 要修改 uid */ +#define PROCINFO_FLAG_SETGID 0x00000020 /* 要修改 gid */ +#define PROCINFO_FLAG_CLOSE_FD 0x00000040 /* 不要導到/dev/null,直接關閉fd*/ +#define PROCINFO_FLAG_LIMIT_MEM 0x00000080 +#define PROCINFO_FLAG_LIMIT_CORE 0x00000100 +#define PROCINFO_FLAG_LIMIT_CPU 0x00000200 +#define PROCINFO_FLAG_LIMIT_OUT 0x00000400 +#define PROCINFO_FLAG_LIMIT_FILE 0x00000800 +#define PROCINFO_FLAG_LIMIT_PROC 0x00001000 +#define PROCINFO_FLAG_LIMIT_STACK 0x00002000 + +/* PROCINFO 裡面 o_result 的值 */ +#define PROCINFO_RESULT_OK 0 +#define PROCINFO_RESULT_RE 1 +#define PROCINFO_RESULT_TLE 2 +#define PROCINFO_RESULT_OLE 3 +#define PROCINFO_RESULT_SLE 4 /* 暫停次數太多 */ +#define PROCINFO_RESULT_AB 5 /* 使用者中斷 */ +#define PROCINFO_RESULT_UD 6 /* 未定義的東西,這應該不會出現吧 */ +#define PROCINFO_RESULT_MAX 7 + +/* setproc_main 的回傳值 */ +#define SCTJUDGE_PROC_EXIT_SUCCESS 0 +#define SCTJUDGE_PROC_EXIT_INVALID 2 /* 不合理的設定值 */ +#define SCTJUDGE_PROC_EXIT_INSECURE 3 /* 不安全的設定值 */ +#define SCTJUDGE_PROC_EXIT_REJECT 4 /* 拒絕不符權限的操作 */ +#define SCTJUDGE_PROC_EXIT_FAILED 5 /* 有些操作失敗了 */ +#define SCTJUDGE_PROC_EXIT_MAX 6 + +int sctproc_main(PROCINFO*); +void sctproc_setdefault(PROCINFO*); +void sctproc_freestring(PROCINFO*); +void sctproc_logger(int, const char*, ...); +void sctproc_err(const char*, ...); + +#endif diff --git a/src/ProcMain.c b/src/ProcMain.c new file mode 100644 index 0000000..d28853c --- /dev/null +++ b/src/ProcMain.c @@ -0,0 +1,1634 @@ +#ifdef HAVE_CONFIG_H +# include "SctConfig.h" +#endif + +#include "SctVersion.h" +#include "SctConst.h" +#include "SctCommon.h" +#include "ProcCommon.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <fcntl.h> +#include <dirent.h> +#include <unistd.h> +#include <errno.h> +#include <grp.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <pthread.h> +#include <semaphore.h> + +#ifdef HAVE_CONF_CAP +#include <sys/prctl.h> +#include <sys/capability.h> +#endif + +#define SCTJUDGE_PROC_CHILD_CLOSE_STDIN 0 +#define SCTJUDGE_PROC_CHILD_OPEN_STDIN_SINK 1 +#define SCTJUDGE_PROC_CHILD_OPEN_STDIN_FILE 2 +#define SCTJUDGE_PROC_CHILD_DUP2_STDIN 3 +#define SCTJUDGE_PROC_CHILD_CLOSE_STDOUT 4 +#define SCTJUDGE_PROC_CHILD_OPEN_STDOUT_SINK 5 +#define SCTJUDGE_PROC_CHILD_OPEN_STDOUT_FILE 6 +#define SCTJUDGE_PROC_CHILD_DUP2_STDOUT 7 +#define SCTJUDGE_PROC_CHILD_CLOSE_STDERR 8 +#define SCTJUDGE_PROC_CHILD_REDIR_STDERR 9 +#define SCTJUDGE_PROC_CHILD_OPEN_STDERR_FILE 10 +#define SCTJUDGE_PROC_CHILD_DUP2_STDERR 11 +#define SCTJUDGE_PROC_CHILD_CLOSE_OTHER_FDS 12 +#define SCTJUDGE_PROC_CHILD_CHROOT 13 +#define SCTJUDGE_PROC_CHILD_SETGID 14 +#define SCTJUDGE_PROC_CHILD_SETGROUPS 15 +#define SCTJUDGE_PROC_CHILD_SETUID 16 +#define SCTJUDGE_PROC_CHILD_RESET_UID 17 +#define SCTJUDGE_PROC_CHILD_CAP_SET_PROC 18 +#define SCTJUDGE_PROC_CHILD_SETRLIMIT_MEM 19 +#define SCTJUDGE_PROC_CHILD_SETRLIMIT_CORE 20 +#define SCTJUDGE_PROC_CHILD_SETRLIMIT_CPU 21 +#define SCTJUDGE_PROC_CHILD_SETRLIMIT_OUT 22 +#define SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE 23 +#define SCTJUDGE_PROC_CHILD_SETRLIMIT_PROC 24 +#define SCTJUDGE_PROC_CHILD_SETRLIMIT_STACK 25 +#define SCTJUDGE_PROC_CHILD_EXEC 26 +#define SCTJUDGE_PROC_CHILD_MAX 27 + + +/* 變數用途在 ProcCommon.h 解釋過了 */ +pid_t child_pid; +pthread_mutex_t child_mutex; + +_Bool checktime_exist; +sem_t checktime_sem; +pthread_t checktime_thread; +pthread_mutex_t checktime_mutex; + +_Bool monitor_exist; +sem_t monitor_sem; +pthread_t monitor_thread; +pthread_mutex_t monitor_mutex; + +_Bool sctproc_tle; +pthread_mutex_t sctproc_tle_mutex; + +pthread_mutex_t output_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* 內部供 logger 和 err 使用的變數 */ +static void (*sctproc_output_debug)(void*, const char*, va_list); +static void (*sctproc_output_normal)(void*, const char*, va_list); +static void (*sctproc_output_err)(void*, const char*, va_list); +static void* sctproc_output_arg; +static int sctproc_verbose; + +/* 如果回傳負數表示失敗,就用這個 */ +static void sctproc_childonly_fail_if_negative(int fd, int rval, int workname){ + int childonly_mesg[2]; + childonly_mesg[0] = workname; + childonly_mesg[1] = (rval < 0) ? errno : 0; + write(fd, &childonly_mesg, sizeof(int) * 2); + if(rval < 0){ + exit(1); + } +} + +/* 如果回傳 NULL 表示失敗,就用這個 */ +static void sctproc_childonly_fail_if_null(int fd, void* rval, int workname){ + int childonly_mesg[2]; + childonly_mesg[0] = workname; + childonly_mesg[1] = (rval == NULL) ? errno : 0; + write(fd, &childonly_mesg, sizeof(int) * 2); + if(rval == NULL){ + exit(2); + } +} + +void sctproc_setdefault(PROCINFO* procinfo){ + procinfo->i_verbose = 1; + procinfo->i_exec = NULL; + procinfo->i_argv = NULL; + procinfo->i_chroot = NULL; + procinfo->i_infile = NULL; + procinfo->i_outfile = NULL; + procinfo->i_errfile = NULL; + procinfo->i_uid = 65535; + procinfo->i_gid = 65535; + procinfo->i_limit_time = 10 * 10000; + procinfo->i_limit_mem = 64 * 1024 * 1024; + procinfo->i_limit_core = 0; + procinfo->i_limit_cpu = 0; + procinfo->i_limit_out = 0; + procinfo->i_limit_file = 0; + procinfo->i_limit_proc = 0; + procinfo->i_limit_stack = 8 * 1024 * 1024; + procinfo->i_func_arg = NULL; + procinfo->i_flag = + PROCINFO_FLAG_SECURE_CHECK | + PROCINFO_FLAG_LIMIT_FILE | + PROCINFO_FLAG_LIMIT_PROC | + PROCINFO_FLAG_LIMIT_CORE; +} + +void sctproc_freestring(PROCINFO* procinfo){ + int i; + + if(procinfo->i_exec != NULL){ + free(procinfo->i_exec); + procinfo->i_exec = NULL; + } + + if(procinfo->i_argv != NULL){ + for(i=0; i<l4da_getlen(procinfo->i_argv); i++){ + if(l4da_v(procinfo->i_argv, char*, i) != NULL){ + free(l4da_v(procinfo->i_argv, char*, i)); + } + } + l4da_free(procinfo->i_argv); + procinfo->i_argv = NULL; + } + + if(procinfo->i_chroot != NULL){ + free(procinfo->i_chroot); + procinfo->i_chroot = NULL; + } + + if(procinfo->i_infile != NULL){ + free(procinfo->i_infile); + procinfo->i_infile = NULL; + } + if(procinfo->i_outfile != NULL){ + free(procinfo->i_outfile); + procinfo->i_outfile = NULL; + } + if(procinfo->i_errfile != NULL){ + free(procinfo->i_errfile); + procinfo->i_chroot = NULL; + } +} + +int sctproc_main(PROCINFO* procinfo){ + + /* private defines */ +#define private_BUFMAX 256 +#define private_READLEN ((sizeof(int))*2) + + sctproc_output_debug = procinfo->f_debug; + sctproc_output_normal = procinfo->f_normal; + sctproc_output_err = procinfo->f_err; + sctproc_output_arg = procinfo->i_func_arg; + sctproc_verbose = procinfo->i_verbose; + + int i; + + int rerr; /* 接收錯誤碼的地方 */ + char errbuf[private_BUFMAX]; /* 系統錯誤訊息緩衝區 */ + + int child_pipe[2]; /* 接收 child process 狀態用的 pipe */ + int child_mesg[2]; /* child process 傳來的訊息,[0]是動作,[1]是 errno */ + int child_status; /* wait 時回傳的 child process 狀態 */ + _Bool child_terminated;/* child process 終止了沒? */ + char* child_workstr; /* 指向 child process 目前狀態訊息的指標 */ + + struct timespec exec_begin; + struct timespec exec_end; + + /* 一些 child process 中專用的變數 */ + int childonly_fdopt; + int childonly_fdcount; + int childonly_fdin; + int childonly_fdout; + int childonly_fderr; + pid_t childonly_pid; + struct rlimit childonly_rlimit; +#ifdef HAVE_CONF_CAP + cap_t childonly_cap; +#endif + + /* 列出設定值,僅供偵錯使用 */ + sctproc_logger(2, "%s: 列出 PROCINFO 中的設定值\n", __func__); + sctproc_logger(2, "%s: i_exec: %s\n", + __func__, STRING_NULL(procinfo->i_exec)); + for(i=0; l4da_v(procinfo->i_argv, char*, i) != NULL; i++){ + sctproc_logger(2, "%s: i_argv[%d]: %s\n", + __func__, i, STRING_NULL(l4da_v(procinfo->i_argv, char*, i))); + } + sctproc_logger(2, "%s: i_chroot: %s\n", + __func__, STRING_NULL(procinfo->i_chroot)); + sctproc_logger(2, "%s: i_infile: %s\n", + __func__, STRING_NULL(procinfo->i_infile)); + sctproc_logger(2, "%s: i_outfile: %s\n", + __func__, STRING_NULL(procinfo->i_outfile)); + sctproc_logger(2, "%s: i_errfile: %s\n", + __func__, STRING_NULL(procinfo->i_errfile)); + sctproc_logger(2, "%s: i_uid: %u\n", __func__, procinfo->i_uid); + sctproc_logger(2, "%s: i_gid: %u\n", __func__, procinfo->i_gid); + sctproc_logger(2, "%s: i_flag: FORCE: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_FORCE)); + sctproc_logger(2, "%s: i_flag: DRYRUN: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_DRYRUN)); + sctproc_logger(2, "%s: i_flag: SECURE_CHECK: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_SECURE_CHECK)); + sctproc_logger(2, "%s: i_flag: REDIR_STDERR: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_REDIR_STDERR)); + sctproc_logger(2, "%s: i_flag: SETUID: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_SETUID)); + sctproc_logger(2, "%s: i_flag: SETGID: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_SETGID)); + sctproc_logger(2, "%s: i_flag: CLOSE_FD: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_CLOSE_FD)); + sctproc_logger(2, "%s: i_flag: LIMIT_MEM: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_MEM)); + sctproc_logger(2, "%s: i_flag: LIMIT_CORE: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_CORE)); + sctproc_logger(2, "%s: i_flag: LIMIT_CPU: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_CPU)); + sctproc_logger(2, "%s: i_flag: LIMIT_OUT: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_OUT)); + sctproc_logger(2, "%s: i_flag: LIMIT_FILE: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_FILE)); + sctproc_logger(2, "%s: i_flag: LIMIT_PROC: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_PROC)); + sctproc_logger(2, "%s: i_flag: LIMIT_STACK: %s\n", __func__, + DISPLAY_YESNO(procinfo->i_flag, PROCINFO_FLAG_LIMIT_STACK)); + sctproc_logger(2, "%s: i_limit_time: %u\n", + __func__, procinfo->i_limit_time); + sctproc_logger(2, "%s: i_limit_mem: %u\n", + __func__, procinfo->i_limit_mem); + sctproc_logger(2, "%s: i_limit_core: %u\n", + __func__, procinfo->i_limit_core); + sctproc_logger(2, "%s: i_limit_cpu: %u\n", + __func__, procinfo->i_limit_cpu); + sctproc_logger(2, "%s: i_limit_out: %u\n", + __func__, procinfo->i_limit_out); + sctproc_logger(2, "%s: i_limit_file: %u\n", + __func__, procinfo->i_limit_file); + sctproc_logger(2, "%s: i_limit_proc: %u\n", + __func__, procinfo->i_limit_proc); + sctproc_logger(2, "%s: i_limit_stack: %u\n", + __func__, procinfo->i_limit_stack); + + /* 檢查 UID == 0 的狀況 */ + sctproc_logger(2, "%s: Check for user ID setting\n", __func__); + if(procinfo->i_uid == 0 && (procinfo->i_flag & PROCINFO_FLAG_SETUID)){ + if(procrealuid == 0){ /* 我們不用 getuid(),因為可能被 swap 過 */ + if(!(procinfo->i_flag & PROCINFO_FLAG_FORCE)){ + sctproc_err("拒絕使用 root 執行受測程式\n"); + return SCTJUDGE_PROC_EXIT_INSECURE; + }else{ + sctproc_logger(1, + "警告:使用 root 身份執行受測程式,所有限制都可能被忽略\n"); + } + }else{ + sctproc_err("非超級使用者不能把 UID 設定為 0\n"); + return SCTJUDGE_PROC_EXIT_REJECT; + } + } + + /* 檢查安全設定值,如果有要求的話。我們不檢查 chroot 目錄是不是空的, + * 因為執行檔應該會複製進去 */ + if(procinfo->i_flag & PROCINFO_FLAG_SECURE_CHECK){ + sctproc_logger(2, "%s: Check for security\n", __func__); + if(procinfo->i_chroot == NULL){ + sctproc_err("您沒有指定 chroot 目錄\n"); + return SCTJUDGE_PROC_EXIT_INSECURE; + } + if(!(procinfo->i_flag & PROCINFO_FLAG_SETUID)){ + sctproc_err("您沒有修改執行受測程式時的使用者\n"); + return SCTJUDGE_PROC_EXIT_INSECURE; + } + if(!(procinfo->i_flag & PROCINFO_FLAG_SETGID)){ + sctproc_err("您沒有修改執行受測程式時的群組\n"); + return SCTJUDGE_PROC_EXIT_INSECURE; + } + if(!(procinfo->i_flag & PROCINFO_FLAG_LIMIT_MEM)){ + sctproc_err("您沒有設定記憶體使用量上限\n"); + return SCTJUDGE_PROC_EXIT_INSECURE; + } + if(!(procinfo->i_flag & PROCINFO_FLAG_LIMIT_CORE)){ + sctproc_err("您沒有設定 core 檔案大小上限\n"); + return SCTJUDGE_PROC_EXIT_INSECURE; + } + if(!(procinfo->i_flag & PROCINFO_FLAG_LIMIT_PROC)){ + sctproc_err("您沒有設定 process 數量上限\n"); + return SCTJUDGE_PROC_EXIT_INSECURE; + } + } + + /* 檢查參數,若強制執行就忽略 */ + if(!(procinfo->i_flag & PROCINFO_FLAG_FORCE)){ + sctproc_logger(2, "%s: Validate process configuration\n", __func__); + if(procinfo->i_exec == NULL){ + sctproc_err("沒有指定受測程式可執行檔名稱\n"); + return SCTJUDGE_PROC_EXIT_INVALID; + } + if(procinfo->i_argv == NULL){ + sctproc_err("沒有指定執行程式時的命令列\n"); + return SCTJUDGE_PROC_EXIT_INVALID; + } + if(procinfo->i_outfile == NULL){ + sctproc_err("沒有指定輸出檔案\n"); + return SCTJUDGE_PROC_EXIT_INVALID; + } + if(procinfo->i_limit_time <= 0){ + sctproc_err("沒有指定執行時間限制\n"); + return SCTJUDGE_PROC_EXIT_INVALID; + } + if((procinfo->i_flag & PROCINFO_FLAG_REDIR_STDERR) && + (procinfo->i_errfile != NULL)){ + sctproc_err("重新導向 stderr 時,不能指定 stderr 要導向的檔案\n"); + return SCTJUDGE_PROC_EXIT_INVALID; + } + } + + + /* 產生 child process */ + sctproc_logger(2, "%s: Creating a pipe for status reporting\n", __func__); + pipe(child_pipe); + sctproc_logger(2, "%s: Creating a child process\n", __func__); + child_pid = fork(); + + if(child_pid < 0){ + rerr = errno; + strerror_threadsafe(rerr, errbuf, private_BUFMAX); + sctproc_logger(2, "%s: fork() = %d\n", __func__, child_pid); + sctproc_logger(2, "%s: errno = %d (%s)\n", __func__, rerr, errbuf); + sctproc_err("無法建立新程序:%s\n", errbuf); + close(child_pipe[0]); + close(child_pipe[1]); + return SCTJUDGE_PROC_EXIT_FAILED; + + }else if(child_pid > 0){ + sctproc_logger(2, "%s: fork() = %d\n", __func__, child_pid); + close(child_pipe[1]); + + /* 持續監測 child process 的狀態 */ + while(read_complete(child_pipe[0], (void*)child_mesg, private_READLEN) + == private_READLEN){ + + strerror_threadsafe(child_mesg[1], errbuf, private_BUFMAX); + + switch(child_mesg[0]){ + case SCTJUDGE_PROC_CHILD_CLOSE_STDIN: + sctproc_logger(2, "%s: child: close(%d), " + "errno = %d (%s)\n", + __func__, STDIN_FILENO, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "關閉標準輸入"); + break; + + case SCTJUDGE_PROC_CHILD_OPEN_STDIN_SINK: + sctproc_logger(2, "%s: child: open(%s, O_RDONLY), " + "errno = %d (%s)\n", + __func__, WITH_NULL, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "開啟輸入檔案 "WITH_NULL); + break; + + case SCTJUDGE_PROC_CHILD_OPEN_STDIN_FILE: + sctproc_logger(2, "%s: child: open(%s, O_RDONLY), " + "errno = %d (%s)\n", + __func__, procinfo->i_infile, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "開啟輸入檔案 %s", + procinfo->i_infile); + break; + + case SCTJUDGE_PROC_CHILD_DUP2_STDIN: + sctproc_logger(2, "%s: child: dup2(<input file>, %d), " + "errno = %d (%s)\n", + __func__, STDIN_FILENO, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "重新導向標準輸入"); + break; + + case SCTJUDGE_PROC_CHILD_CLOSE_STDOUT: + sctproc_logger(2, "%s: child: close(%d), " + "errno = %d (%s)\n", + __func__, STDOUT_FILENO, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "關閉標準輸出"); + break; + + case SCTJUDGE_PROC_CHILD_OPEN_STDOUT_SINK: + sctproc_logger(2, "%s: child: open(%s, " + "O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), " + "errno = %d (%s)\n", + __func__, WITH_NULL, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "開啟輸出檔案"WITH_NULL); + break; + + case SCTJUDGE_PROC_CHILD_OPEN_STDOUT_FILE: + sctproc_logger(2, "%s: child: open(%s, " + "O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), " + "errno = %d (%s)\n", + __func__, procinfo->i_outfile, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "開啟輸出檔案 %s", + procinfo->i_outfile); + break; + + case SCTJUDGE_PROC_CHILD_DUP2_STDOUT: + sctproc_logger(2, "%s: child: dup2(<output file>, %d), " + "errno = %d (%s)\n", + __func__, STDOUT_FILENO, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "重新導向標準輸出"); + break; + + case SCTJUDGE_PROC_CHILD_CLOSE_STDERR: + sctproc_logger(2, "%s: child: close(%d), " + "errno = %d (%s)\n", + __func__, STDERR_FILENO, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "關閉標準錯誤"); + break; + + case SCTJUDGE_PROC_CHILD_REDIR_STDERR: + sctproc_logger(2, "%s: child: dup2(<output file>, %d), " + "errno = %d (%s)\n", + __func__, STDERR_FILENO, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "將標準輸出導向標準錯誤"); + break; + + case SCTJUDGE_PROC_CHILD_OPEN_STDERR_FILE: + sctproc_logger(2, "%s: child: open(%s, " + "O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), " + "errno = %d (%s)\n", + __func__, procinfo->i_errfile, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "開啟錯誤檔案 %s", + procinfo->i_errfile); + break; + + case SCTJUDGE_PROC_CHILD_DUP2_STDERR: + sctproc_logger(2, "%s: child: dup2(<err file>, %d), " + "errno = %d (%s)\n", + __func__, procinfo->i_errfile, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "重新導向標準錯誤"); + break; + + case SCTJUDGE_PROC_CHILD_CLOSE_OTHER_FDS: + sctproc_logger(2, "%s: child: for(i=3; i<getdtablesize();" + " i++){ close(i); }, errno = %d (%s)\n", + __func__, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "關閉所有其他已開啟的檔案"); + break; + + case SCTJUDGE_PROC_CHILD_CHROOT: + sctproc_logger(2, "%s: child: chroot(\"%s\"), " + "errno = %d (%s)\n", + __func__, procinfo->i_chroot, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "切換根目錄至 %s", + procinfo->i_chroot); + break; + + case SCTJUDGE_PROC_CHILD_SETGID: + sctproc_logger(2, "%s: child: setgid(%u), " + "errno = %d (%s)\n", + __func__, procinfo->i_gid, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定 GID 為 %u", + procinfo->i_gid); + break; + + case SCTJUDGE_PROC_CHILD_SETGROUPS: + sctproc_logger(2, "%s: child: setgroups(1, { %u }), " + "errno = %d (%s)\n", + __func__, procinfo->i_gid, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定 supplementary GID 為 " + "%u", procinfo->i_gid); + break; + + case SCTJUDGE_PROC_CHILD_SETUID: + case SCTJUDGE_PROC_CHILD_RESET_UID: + sctproc_logger(2, "%s: child: setuid(%u), " + "errno = %d (%s)\n", + __func__, procinfo->i_uid, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定 UID 為 %d\n", + procinfo->i_uid); + break; + + case SCTJUDGE_PROC_CHILD_CAP_SET_PROC: + sctproc_logger(2, "%s: child: cap_set_proc(), " + "errno = %d (%s)\n", + __func__, child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "Drop Linux capabilites"); + break; + + case SCTJUDGE_PROC_CHILD_SETRLIMIT_MEM: + sctproc_logger(2, "%s: child: setrlimit(RLIMIT_AS, " + "{ %u, %u }), errno = %d (%s)\n", + __func__, procinfo->i_limit_mem, procinfo->i_limit_mem, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定記憶體用量限制"); + break; + + case SCTJUDGE_PROC_CHILD_SETRLIMIT_CORE: + sctproc_logger(2, "%s: child: setrlimit(RLIMIT_CORE, " + "{ %u, %u }), errno = %d (%s)\n", + __func__, procinfo->i_limit_core, + procinfo->i_limit_core, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定 core dump 大小限制"); + break; + + case SCTJUDGE_PROC_CHILD_SETRLIMIT_CPU: + sctproc_logger(2, "%s: child: setrlimit(RLIMIT_CPU, " + "{ %u, %u }), errno = %d (%s)\n", + __func__, procinfo->i_limit_cpu, procinfo->i_limit_cpu, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定 CPU 時間限制"); + break; + + case SCTJUDGE_PROC_CHILD_SETRLIMIT_OUT: + sctproc_logger(2, "%s: child: setrlimit(RLIMIT_FSIZE, " + "{ %u, %u }), errno = %d (%s)\n", + __func__, procinfo->i_limit_out, procinfo->i_limit_out, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定輸出大小限制"); + break; + + case SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE: + sctproc_logger(2, "%s: child: setrlimit(RLIMIT_NOFILE, " + "{ %u, %u }), errno = %d (%s)\n", + __func__, procinfo->i_limit_file, + procinfo->i_limit_file, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定檔案開啟數量限制"); + break; + + case SCTJUDGE_PROC_CHILD_SETRLIMIT_PROC: + sctproc_logger(2, "%s: child: setrlimit(RLIMIT_NPROC, " + "{ %u, %u }), errno = %d (%s)\n", + __func__, procinfo->i_limit_proc, + procinfo->i_limit_proc, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定程序數量限制"); + break; + + case SCTJUDGE_PROC_CHILD_SETRLIMIT_STACK: + sctproc_logger(2, "%s: child: setrlimit(RLIMIT_STACK, " + "{ %u, %u }), errno = %d (%s)\n", + __func__, procinfo->i_limit_stack, + procinfo->i_limit_stack, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "設定 stack 大小限制"); + break; + + case SCTJUDGE_PROC_CHILD_EXEC: + sctproc_logger(2, "%s: child: execv(\"%s\", ...), " + "errno = %d (%s)\n", + __func__, procinfo->i_exec, procinfo->i_exec, + child_mesg[1], errbuf); + sprintf_malloc(&child_workstr, "執行受測程式 %s", + procinfo->i_exec); + break; + + default: + sctproc_logger(2, "%s: child: unknown message\n", __func__); + sprintf_malloc(&child_workstr, "不明的訊息"); + } + + if(child_mesg[1]){ + sctproc_err("子程序傳回錯誤:%s:%s\n", child_workstr, errbuf); + free(child_workstr); + sctproc_logger(2, "%s: Kill the child process NOW!", __func__); + kill(child_pid, SIGKILL); + sctproc_logger(2, "%s: Wait for the child process", __func__); + waitpid(child_pid, NULL, 0); + close(child_pipe[0]); + return SCTJUDGE_PROC_EXIT_FAILED; + + }else{ + free(child_workstr); + } + } + + /* exec 成功,正式啟動所有 judge 所需的東西 */ + sctproc_logger(2, "%s: 受測程式已成功執行\n", __func__); + clock_gettime(CLOCK_REALTIME, &exec_begin); + sctproc_logger(2, "%s: 現在時間是 (timespec) %d.%ld\n", __func__, + exec_begin.tv_sec, exec_begin.tv_nsec); + close(child_pipe[0]); + + /* 初始化所有同步用的東西 */ + sctproc_logger(2, "%s: Initialize mutexes and semaphores\n", __func__); + pthread_mutex_init(&checktime_mutex, NULL); + pthread_mutex_init(&sctproc_tle_mutex, NULL); + sem_init(&checktime_sem, 0, 0); + if(procinfo->i_verbose > 0){ + pthread_mutex_init(&monitor_mutex, NULL); + sem_init(&monitor_sem, 0, 0); + } + + /* 產生 thread,一樣要有 verbose > 0 才有 monitor */ + sctproc_logger(2, "%s: Create thread: checktime\n", __func__); + pthread_create(&checktime_thread, NULL, &sctproc_checktime, procinfo); + pthread_mutex_lock(&checktime_mutex); + checktime_exist = 1; + pthread_mutex_unlock(&checktime_mutex); + if(procinfo->i_verbose > 0){ + sctproc_logger(2, "%s: Create thread: monitor\n", __func__); + pthread_create(&monitor_thread, NULL, &sctproc_monitor, procinfo); + pthread_mutex_lock(&monitor_mutex); + monitor_exist = 1; + pthread_mutex_unlock(&monitor_mutex); + } + + /* 開始要 wait 了,莫名其妙 stop 的程式我最多只會送 + * SCTJUDGE_PROC_MAX_STOP_TIMES 次 SIGCONT 給你(避免進入無限迴圈) + * 這種奇怪的程式就等著 SLE 吧 + * (這個常數定義在 SctConst.h,我想這不是很重要的東西)*/ + for(i=0, child_terminated=0; i<= SCTJUDGE_PROC_MAX_STOP_TIMES; i++){ + wait4(child_pid, &child_status, WUNTRACED, &procinfo->o_rusage); + if(WIFSTOPPED(child_status) && i!=SCTJUDGE_PROC_MAX_STOP_TIMES){ + sctproc_logger(1, "受測程式因訊號 %d 而暫停,自動傳送 SIGCONT " + "(第 %d 次,最多可傳 %d 次)\n", WSTOPSIG(child_status), + i+1, SCTJUDGE_PROC_MAX_STOP_TIMES); + kill(child_pid, SIGCONT); + }else{ + if(WIFSIGNALED(child_status)){ + clock_gettime(CLOCK_REALTIME, &exec_end); + child_terminated = 1; + procinfo->o_signal = WTERMSIG(child_status); + if(procinfo->o_signal == SIGXFSZ){ + procinfo->o_result = PROCINFO_RESULT_OLE; + }else{ + procinfo->o_result = PROCINFO_RESULT_RE; + } + procinfo->o_procexit = WEXITSTATUS(child_status); + break; + } + if(WIFEXITED(child_status)){ + clock_gettime(CLOCK_REALTIME, &exec_end); + child_terminated = 1; + procinfo->o_result = PROCINFO_RESULT_OK; + procinfo->o_procexit = WEXITSTATUS(child_status); + break; + } + sctproc_logger(2, "%s: The child process is terminated\n", + __func__); + } + } + + /* 暫停次數超過限制 */ + if(!child_terminated){ + sctproc_logger(2, "%s: Sending SIGKILL\n", __func__); + kill(child_pid, SIGKILL); + wait4(child_pid, &child_status, WUNTRACED, &procinfo->o_rusage); + procinfo->o_result = PROCINFO_RESULT_SLE; + procinfo->o_procexit = WEXITSTATUS(child_status); + clock_gettime(CLOCK_REALTIME, &exec_end); + } + + + /* 通知兩個 thread,請他們結束,也等他們結束 */ + pthread_mutex_lock(&checktime_mutex); + if(checktime_exist){ + checktime_exist = 0; + pthread_mutex_unlock(&checktime_mutex); + sctproc_logger(2, "%s: Wait for thread checktime to exit\n", + __func__); + sem_post(&checktime_sem); + pthread_join(checktime_thread, NULL); + }else{ + pthread_mutex_unlock(&checktime_mutex); + } + + pthread_mutex_lock(&monitor_mutex); + if(monitor_exist){ + monitor_exist = 0; + pthread_mutex_unlock(&monitor_mutex); + sctproc_logger(2, "%s: Wait for thread monitor to exit\n", + __func__); + sem_post(&monitor_sem); + pthread_join(monitor_thread, NULL); + }else{ + pthread_mutex_unlock(&monitor_mutex); + } + + /* 判斷 TLE 和執行時間 */ + difftimespec(&exec_begin, &exec_end, &(procinfo->o_runtime)); + checktimespec(&(procinfo->o_runtime)); + + pthread_mutex_lock(&sctproc_tle_mutex); + if(sctproc_tle){ + procinfo->o_result = PROCINFO_RESULT_TLE; + } + pthread_mutex_unlock(&sctproc_tle_mutex); + + if(sctproc_abort){ + procinfo->o_result = PROCINFO_RESULT_AB; + } + + /* 移除掉所有同步用的東西 */ + sctproc_logger(2, "%s: Destroy mutexes and semaphores\n", __func__); + pthread_mutex_destroy(&child_mutex); + pthread_mutex_destroy(&checktime_mutex); + pthread_mutex_destroy(&monitor_mutex); + pthread_mutex_destroy(&sctproc_tle_mutex); + sem_destroy(&checktime_sem); + sem_destroy(&monitor_sem); + + }else{ + /* 這裡是 child process,也是要用到 setuid 的地方 + * 因為我們在 main() 就已經停用 setuid 了 + * 所以我們必須在這段中需要的地方啟用他 + * 如果是用 Linux capabilites 舊部需要做而外處理 + * 如 */ + + close(child_pipe[0]); + + /* close-on-exec 用來關閉這個暫時的資料傳輸通道 */ + childonly_fdopt = fcntl(child_pipe[1], F_GETFD); + fcntl(child_pipe[1], F_SETFD, childonly_fdopt | FD_CLOEXEC); + + /* 設為獨立的 process group,不過失敗就算了,這不重要 */ + childonly_pid = getpid(); + setpgid(childonly_pid, childonly_pid); + + /* 重設所有 signal handler + * XXX 我猜 signal 最大值是 31,還有其他更好的寫法嗎? */ + for(i=1; i<=31; i++){ + signal(i, SIG_DFL); + } + + + /* 開啟或關閉輸入檔案 */ + if(procinfo->i_infile == NULL && + (procinfo->i_flag & PROCINFO_FLAG_CLOSE_FD)){ + sctproc_childonly_fail_if_negative(child_pipe[1], + close(STDIN_FILENO), + SCTJUDGE_PROC_CHILD_CLOSE_STDIN); + }else{ + if(procinfo->i_infile == NULL){ + sctproc_childonly_fail_if_negative(child_pipe[1], + childonly_fdin = open(WITH_NULL, O_RDONLY), + SCTJUDGE_PROC_CHILD_OPEN_STDIN_SINK); + }else{ + sctproc_childonly_fail_if_negative(child_pipe[1], + childonly_fdin = open(procinfo->i_infile, O_RDONLY), + SCTJUDGE_PROC_CHILD_OPEN_STDIN_FILE); + } + sctproc_childonly_fail_if_negative(child_pipe[1], + dup2(childonly_fdin, STDIN_FILENO), + SCTJUDGE_PROC_CHILD_DUP2_STDIN); + } + + /* 開啟或關閉輸出檔案 */ + childonly_fdout = -1; + if(procinfo->i_outfile == NULL && + (procinfo->i_flag & PROCINFO_FLAG_CLOSE_FD)){ + sctproc_childonly_fail_if_negative(child_pipe[1], + close(STDOUT_FILENO), + SCTJUDGE_PROC_CHILD_CLOSE_STDOUT); + }else{ + if(procinfo->i_outfile == NULL){ + sctproc_childonly_fail_if_negative(child_pipe[1], + childonly_fdout = open(WITH_NULL, + O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), + SCTJUDGE_PROC_CHILD_OPEN_STDOUT_SINK); + }else{ + sctproc_childonly_fail_if_negative(child_pipe[1], + childonly_fdout = open(procinfo->i_outfile, + O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), + SCTJUDGE_PROC_CHILD_OPEN_STDOUT_FILE); + } + sctproc_childonly_fail_if_negative(child_pipe[1], + dup2(childonly_fdout, STDOUT_FILENO), + SCTJUDGE_PROC_CHILD_DUP2_STDOUT); + } + + /* 開啟或關閉標準錯誤檔案 */ + if(procinfo->i_errfile == NULL){ + if(procinfo->i_flag & PROCINFO_FLAG_CLOSE_FD){ + sctproc_childonly_fail_if_negative(child_pipe[1], + close(STDERR_FILENO), + SCTJUDGE_PROC_CHILD_CLOSE_STDERR); + } + if(procinfo->i_flag & PROCINFO_FLAG_REDIR_STDERR && + childonly_fdout >= 0){ + sctproc_childonly_fail_if_negative(child_pipe[1], + dup2(childonly_fdout, STDERR_FILENO), + SCTJUDGE_PROC_CHILD_REDIR_STDERR); + } + }else{ + sctproc_childonly_fail_if_negative(child_pipe[1], + childonly_fderr = open(procinfo->i_errfile, + O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR), + SCTJUDGE_PROC_CHILD_OPEN_STDERR_FILE); + sctproc_childonly_fail_if_negative(child_pipe[1], + dup2(childonly_fderr, STDERR_FILENO), + SCTJUDGE_PROC_CHILD_DUP2_STDERR); + } + + /* 很暴力地把所有不該留著的 fd 關掉 */ + childonly_fdcount = getdtablesize(); + for(i=3; i<childonly_fdcount; i++){ + close(i); + } + child_mesg[0] = SCTJUDGE_PROC_CHILD_CLOSE_OTHER_FDS; + child_mesg[1] = 0; + write(child_pipe[1], &child_mesg, sizeof(int) * 2); + + /* 接下來的操作可能需要提升的權限 */ +#ifndef HAVE_CONF_CAP + enable_setuid(); +#endif + + /* 進行 chroot */ + if(procinfo->i_chroot != NULL){ + sctproc_childonly_fail_if_negative(child_pipe[1], + chroot(procinfo->i_chroot), + SCTJUDGE_PROC_CHILD_CHROOT); + chdir("/"); + } + +#ifdef HAVE_CONF_CAP + /* 確保修改身份的時候 capabilities 仍然留著 + * 等一下再自己把 capabilities 丟掉 */ + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); +#endif + + /* 我要 setgid 嗎? */ + if(procinfo->i_flag & PROCINFO_FLAG_SETGID){ + sctproc_childonly_fail_if_negative(child_pipe[1], + setgid(procinfo->i_gid), + SCTJUDGE_PROC_CHILD_SETGID); + sctproc_childonly_fail_if_negative(child_pipe[1], + setgroups(1, &(procinfo->i_gid)), + SCTJUDGE_PROC_CHILD_SETGROUPS); + } + + /* 我要 setuid 嗎? */ + if(procinfo->i_flag & PROCINFO_FLAG_SETGID){ + sctproc_childonly_fail_if_negative(child_pipe[1], + setuid(procinfo->i_uid), + SCTJUDGE_PROC_CHILD_SETUID); + } + else{ + /* 即使不要求 setuid,也不能讓受測程式拿到 setuid root */ + sctproc_childonly_fail_if_negative(child_pipe[1], + setuid(procrealuid), + SCTJUDGE_PROC_CHILD_RESET_UID); + } + + +#ifdef HAVE_CONF_CAP + /* 丟掉所有 Linux capabilities */ + childonly_cap = cap_init(); + cap_clear_flag(childonly_cap, CAP_EFFECTIVE); + cap_clear_flag(childonly_cap, CAP_INHERITABLE); + cap_clear_flag(childonly_cap, CAP_PERMITTED); + sctproc_childonly_fail_if_negative(child_pipe[1], + cap_set_proc(childonly_cap), + SCTJUDGE_PROC_CHILD_CAP_SET_PROC); + /* 需要 drop bound 嗎?再考慮看看 */ +#endif + + /* 至此所有特殊權力都已丟棄 */ + + + /* 開始設定資源限制 */ + + /* -- MEM -- */ + if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_MEM){ + childonly_rlimit.rlim_cur = procinfo->i_limit_mem; + childonly_rlimit.rlim_max = procinfo->i_limit_mem; + sctproc_childonly_fail_if_negative(child_pipe[1], + setrlimit(RLIMIT_AS, &childonly_rlimit), + SCTJUDGE_PROC_CHILD_SETRLIMIT_MEM); + } + + /* -- CORE -- */ + if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_CORE){ + childonly_rlimit.rlim_cur = procinfo->i_limit_core; + childonly_rlimit.rlim_max = procinfo->i_limit_core; + sctproc_childonly_fail_if_negative(child_pipe[1], + setrlimit(RLIMIT_CORE, &childonly_rlimit), + SCTJUDGE_PROC_CHILD_SETRLIMIT_CORE); + } + + /* -- CPU -- */ + if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_CPU){ + childonly_rlimit.rlim_cur = procinfo->i_limit_cpu; + childonly_rlimit.rlim_max = procinfo->i_limit_cpu; + sctproc_childonly_fail_if_negative(child_pipe[1], + setrlimit(RLIMIT_CPU, &childonly_rlimit), + SCTJUDGE_PROC_CHILD_SETRLIMIT_CPU); + } + + /* -- OUT -- */ + if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_OUT){ + childonly_rlimit.rlim_cur = procinfo->i_limit_out; + childonly_rlimit.rlim_max = procinfo->i_limit_out; + sctproc_childonly_fail_if_negative(child_pipe[1], + setrlimit(RLIMIT_FSIZE, &childonly_rlimit), + SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE); + } + + /* -- FILE -- */ + if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_FILE){ + childonly_rlimit.rlim_cur = procinfo->i_limit_file; + childonly_rlimit.rlim_max = procinfo->i_limit_file; + sctproc_childonly_fail_if_negative(child_pipe[1], + setrlimit( +#ifdef RLIMIT_NOFILE + RLIMIT_NOFILE +#else + RLIMIT_OFILE +#endif + , &childonly_rlimit), + SCTJUDGE_PROC_CHILD_SETRLIMIT_FILE); + } + + /* -- PROC -- */ + if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_PROC){ + childonly_rlimit.rlim_cur = procinfo->i_limit_proc; + childonly_rlimit.rlim_max = procinfo->i_limit_proc; + sctproc_childonly_fail_if_negative(child_pipe[1], + setrlimit(RLIMIT_NPROC, &childonly_rlimit), + SCTJUDGE_PROC_CHILD_SETRLIMIT_PROC); + } + + /* -- STACK -- */ + if(procinfo->i_flag & PROCINFO_FLAG_LIMIT_STACK){ + childonly_rlimit.rlim_cur = procinfo->i_limit_stack; + childonly_rlimit.rlim_max = procinfo->i_limit_stack; + sctproc_childonly_fail_if_negative(child_pipe[1], + setrlimit(RLIMIT_STACK, &childonly_rlimit), + SCTJUDGE_PROC_CHILD_SETRLIMIT_STACK); + } + + /* 一切都完成了,現在要 exec 了! */ + sctproc_childonly_fail_if_negative(child_pipe[1], + execv(procinfo->i_exec, l4da_data(procinfo->i_argv)), + SCTJUDGE_PROC_CHILD_EXEC); + } + + + return SCTJUDGE_PROC_EXIT_SUCCESS; + + /* drop private defines */ +#undef private_BUFMAX +#undef private_READLEN + +} + +void sctproc_logger(int level, const char* format, ...){ + va_list ap; + va_start(ap, format); + if(sctproc_verbose >= level){ + pthread_mutex_lock(&output_mutex); + if(level >= 2){ + (*sctproc_output_debug)(sctproc_output_arg, format, ap); + }else{ + (*sctproc_output_normal)(sctproc_output_arg, format, ap); + } + pthread_mutex_unlock(&output_mutex); + } + va_end(ap); +} + +void sctproc_err(const char* format, ...){ + va_list ap; + va_start(ap, format); + pthread_mutex_lock(&output_mutex); + (*sctproc_output_err)(sctproc_output_arg, format, ap); + pthread_mutex_unlock(&output_mutex); + va_end(ap); +} + +#if 0 +static const char* childmsg_text[SCTCHILD_MSGMAX] = { + "開啟輸入檔案", + "重新導向標準輸入", + "開啟輸出檔案", + "重新導向標準輸出", + "開啟用於導向標準錯誤的檔案", + "重新導向標準錯誤", + "關閉所有不使用的檔案", + "設定記憶體限制", + "設定輸出限制", + "設定禁止開啟其他檔案", + "設定禁止產生新程序", + "執行 chroot", + "修改 real 和 effective UID", + "修改 real 和 effective GID", + "修改 supplementary GIDs", + "丟棄所有 Linux capabilities", + "執行受測程式" +}; + +static void sctjudge_makechild_cleanup_p1(void){ + pthread_mutex_lock(&tkill_mx); + if(tkill_yes){ + tkill_yes = 0; + pthread_mutex_unlock(&tkill_mx); + sem_post(&tlethr); + }else{ + pthread_mutex_unlock(&tkill_mx); + } + + pthread_mutex_lock(&tdisplay_mx); + if(tdisplay_yes){ + tdisplay_yes = 0; + pthread_mutex_unlock(&tdisplay_mx); + sem_post(&dispthr); + }else{ + pthread_mutex_unlock(&tdisplay_mx); + } + +} + +static void sctjudge_makechild_cleanup_p2(mcopt, oldperm, copiedexe) + const struct makechildopt* mcopt; + const mode_t oldperm; + char* copiedexe; +{ + if(oldperm != -1 && mcopt->chrootdir != NULL){ + if(mcopt->flags & SCTMC_VERBOSE){ + printf("恢復 %s 的權限\n", mcopt->chrootdir); + } + chmod(mcopt->chrootdir, oldperm); + } + if(copiedexe != NULL && mcopt->chrootdir != NULL && + !(mcopt->flags & SCTMC_NOCOPY)){ + if(mcopt->flags & SCTMC_VERBOSE){ + printf("移除 %s......\n", copiedexe); + } + unlink(copiedexe); + free(copiedexe); + } +} + +static void sctjudge_makechild_cleanup(mcopt, oldperm, copiedexe) + const struct makechildopt* mcopt; + const mode_t oldperm; + char* copiedexe; +{ + sctjudge_makechild_cleanup_p1(); + sctjudge_makechild_cleanup_p2(mcopt, oldperm, copiedexe); +} + +static int copyfilebyname(const char* src, const char* destdir + , char** srcsn, char** destfn){ + /* strerror 並不 thread safe,因此必須確定只有一個 thread 會用到 */ + /* 為了確保安全,要求目的地目錄必須是空的 */ + DIR* dirp; + if((dirp=opendir(destdir)) == NULL){ + fprintf(stderr, "無法開啟目錄 %s:%s\n", destdir, strerror(errno)); + return -1; + } + struct dirent* entryp; + while((entryp=readdir(dirp)) != NULL){ + if(entryp->d_name[0] != '.' || + (entryp->d_name[1] != '.' && entryp->d_name[1] != '\0')){ + fprintf(stderr, "拒絕複製檔案:目錄 %s 不是空的\n", destdir); + return -1; + } + } + int fd, fd2; + if((fd=open(src, O_RDONLY)) < 0){ + fprintf(stderr, "無法讀取檔案 %s:%s\n", src, strerror(errno)); + return -1; + } + const int bufsize = 4096; + void* buf = malloc(bufsize); + char* srcshortname; + char* tmp; + if((tmp=strrchr(src, '/')) == NULL){ + srcshortname = (char*)src; + }else{ + srcshortname = tmp + 1; + } + *srcsn = srcshortname; + char* destfilename = (char*) + malloc(strlen(srcshortname) + strlen(destdir) + 1); + sprintf(destfilename, "%s/%s", destdir, srcshortname); + *destfn = destfilename; + /* 新檔案權限是 0755,umask 亂弄的我可不管 */ + if((fd2=open(destfilename, O_CREAT | O_WRONLY | O_EXCL, + S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) < 0){ + fprintf(stderr, "無法建立檔案 %s:%s\n", + destfilename, strerror(errno)); + free(buf); + return -1; + } + int readcount; + while((readcount = read(fd, buf, bufsize)) > 0){ + write(fd2, buf, readcount); + } + close(fd); + close(fd2); + free(buf); + return 0; +} + +static void sctjudge_list_settings(const struct makechildopt* mcopt){ + printf("\t受測程式可執行檔名稱:%s", mcopt->executable); + if(mcopt->flags & SCTMC_NOCOPY && mcopt->chrootdir != NULL){ + printf(" (相對於 %s)\n" + "\t受測程式執行時的根目錄:%s\n" + "\t執行前複製檔案:否\n" + , mcopt->chrootdir, mcopt->chrootdir); + }else{ + putchar('\n'); + if(mcopt->chrootdir != NULL){ + printf("\t受測程式執行時的根目錄:%s\n" + "\t執行前複製檔案:是\n" + , mcopt->chrootdir); + }else{ + puts("\t受測程式執行時的根目錄:/\n" + "\t執行前複製檔案:否"); + } + } + printf("\t輸入檔案:%s\n" + "\t輸出檔案:%s" + , mcopt->inputfile, mcopt->outputfile); + if(mcopt->flags & SCTMC_REDIR_STDERR){ + puts(" (重新導向標準輸出和標準錯誤)"); + }else{ + puts(" (重新導向標準輸出)"); + } + printf("\t執行時間限制:%d 毫秒\n", mcopt->exectime); + fputs("\t記憶體使用量限制:", stdout); + if(mcopt->memlimit > 0){ + printf("%d 位元組\n", mcopt->memlimit); + }else{ + puts("無限制"); + } + fputs("\t輸出限制:", stdout); + if(mcopt->outlimit > 0){ + printf("%d 位元組\n", mcopt->outlimit); + }else{ + puts("無限制"); + } + if(mcopt->flags & SCTMC_SETUID){ + printf("\t設定受測程式執行時的 UID 為 %u\n", mcopt->uid); + }else{ + puts("\t執行受測程式時維持原有的 real UID 和 effective UID"); + } + if(mcopt->flags & SCTMC_SETGID){ + printf("\t設定受測程式執行時的 GID 為 %u\n", mcopt->gid); + }else{ + puts("\t執行受測程式時維持原有的 real GID、effective GID " + "和 supplementary GID"); + } +} + +static int read_size(int fd, void* buf, size_t count){ + /* 一定要讀到 count 位元組的資料才會回傳,所以只要回傳 < count + * 就表示已經讀到 EOF 了 */ + char* bufp = (char*)buf; + size_t curcount = 0; + int rval; + do{ + rval = read(fd, bufp, count - curcount); + if(rval < 0){ + return rval; + }else if(rval == 0){ + return curcount; + }else{ + bufp += rval; + curcount += rval; + } + }while(curcount < count); + return count; +} + +void* sctjudge_makechild(void* arg){ + /* 首先等所有 thread 都啟動完成再開始動作 */ + sem_wait(&mcthr); + + /* 宣告變數囉! */ + int i; + + /* 函式參數與回傳值,Judge 結果就當作回傳值 */ + struct makechildopt mcopt = *(struct makechildopt*)arg; + struct makechildrval* toreturn = + (struct makechildrval*)malloc(sizeof(struct makechildrval)); + memset(toreturn, 0, sizeof(struct makechildrval)); + + /* 可執行檔名稱儲存區 */ + char* execshortname = NULL; /* 可以說就是 basename,chroot+copy 才會用 */ + char* execdestname = NULL; /* 複製出來的暫時執行檔,chroot+copy 才會用 */ + /* 這幾個變數用來儲存 Judge 結果,如果沒有啟用 verbose + * 且一切正常,那這些是唯一會輸出的東西了,目的是輸出容 + * 易被其他程式分析 */ + + /* 要 chroot 的目錄的相關資訊 */ + struct stat chrdirinfo; /* 取得原始的 mode 用的 */ + mode_t chrdir_oldmode = -1; /* 這樣我們才能在結束時 chmod 回去 */ + + /* 子程序的 PID,因為不會變,所以複製一份下來就不用去全域變數拿的 */ + pid_t pidcopy; + + /* 和子程序溝通用的 */ + int childpipe[2]; + int childmsg[2]; /* 每個訊息的大小都是 sizeof(int)*2 + [0]=發生事情的名稱 [1]=errno */ + int readsize = sizeof(int) * 2; + + /* 計時用的 */ + struct timespec execstart, execfinish; + + /* 子程序結束狀態 */ + int childexitstat; + char childterminated = 0; + + /* 子程序才用的到的變數 */ + int fdinput, fdoutput, fderr, childressize; + const int fdtablesize = getdtablesize(); + struct rlimit childres; + + /* 變數宣告完了 */ + + if(mcopt.flags & SCTMC_VERBOSE){ + puts("列出設定值:"); + sctjudge_list_settings(&mcopt); + fflush(stdout); + } + if(mcopt.flags & SCTMC_DRYRUN){ + abort_makechild(SCTMCRVAL_SUCCESS); + } + + /* 我們要開始做正事了 */ + /* 由此開始,我是唯一會用到 strerror() 的 thread + * 所以說應該沒有問題 */ + if(mcopt.chrootdir != NULL && !(mcopt.flags & SCTMC_NOCOPY)){ + /* 必須要複製可執行檔 */ + if(mcopt.flags & SCTMC_VERBOSE){ + puts("開始複製可執行檔......"); + } + if(copyfilebyname(mcopt.executable, mcopt.chrootdir, &execshortname + , &execdestname) < 0){ + abort_makechild(SCTMCRVAL_PREPARE); + } + } + /* 再來是 chroot 的問題,避免受測程式亂開目錄之類的, + * 所以把權限設成 0555,不過如果沒有變更 uid 和 gid , + * 受測程式還是可以 chmod 回來 */ + if(mcopt.chrootdir != NULL){ + if(mcopt.flags & SCTMC_VERBOSE){ + puts("取得用於 chroot 的目錄相關資訊"); + } + if(stat(mcopt.chrootdir, &chrdirinfo) < 0){ + fprintf(stderr, "無法取得目錄 %s 的相關資訊:%s\n", + mcopt.chrootdir, strerror(errno)); + abort_makechild(SCTMCRVAL_PREPARE); + }else{ + if((chrdirinfo.st_mode & S_IFMT) == S_IFDIR){ + chrdir_oldmode = chrdirinfo.st_mode & + (S_ISUID |S_ISGID |S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO); + }else{ + fprintf(stderr, "%s 並不是目錄\n", mcopt.chrootdir); + abort_makechild(SCTMCRVAL_PREPARE); + } + } + if(mcopt.flags & SCTMC_VERBOSE){ + puts("嘗試變更此目錄的權限"); + } + if(chmod(mcopt.chrootdir, + S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0){ + fprintf(stderr, "無法變更目錄 %s 的權限\n", mcopt.chrootdir); + abort_makechild(SCTMCRVAL_PREPARE); + } + } + + fflush(stdout); +#ifndef HAVE_CONF_CAP + enable_setuid(); +#endif + /* 建立用來和 chlid process 溝通的 pipe */ + pipe(childpipe); + pthread_mutex_lock(&pidmutex); +#ifdef HAVE_WORKING_FORK + pidchild = fork(); +#else +#error "This program requires a working fork function." +#endif + if(pidchild < 0){ +#ifndef HAVE_CONF_CAP + disable_setuid(); +#endif + pthread_mutex_unlock(&pidmutex); + fprintf(stderr, "子程序建立失敗:%s\n", strerror(errno)); + abort_makechild(SCTMCRVAL_FORK); + } + else if(pidchild > 0){ + /* 我們原本的 thread */ + /* 從這裡開始,我們需要 setuid 來 kill 子程序, + * 所以說不要執行 disable_setuid() */ + pidcopy = pidchild; + pthread_mutex_unlock(&pidmutex); + close(childpipe[1]); + /* 從 pipe 接收 child process 狀態,每次固定要讀 sizeof(int)*2 */ + while(read_size(childpipe[0], (void*)childmsg, readsize) == readsize){ + if(childmsg[0] < 0 || childmsg[0] >= SCTCHILD_MSGMAX){ + /* 會發生嗎?除非被亂七八糟 ptrace 吧 + * 這時候先砍掉子程序再說 */ + kill(pidcopy, SIGKILL); + close(childpipe[0]); + abort_makechild(SCTMCRVAL_INVALID); + }else{ + if(childmsg[1]){ + fprintf(stderr, "子程序[%d]發生錯誤:%s:%s\n", + pidcopy, childmsg_text[childmsg[0]], + strerror(childmsg[1])); + kill(pidcopy, SIGKILL); + close(childpipe[0]); + abort_makechild(SCTMCRVAL_CHILDFAIL); + }else{ + if(mcopt.flags & SCTMC_VERBOSE){ + printf("子程序[%d]:%s\n", pidcopy, + childmsg_text[childmsg[0]]); + } + } + } + } + clock_gettime(CLOCK_REALTIME, &execstart); + close(childpipe[0]); + + /* 現在我們確定受測程式已經在執行了,所以需要記錄一下時間 + * 通知其他 thread */ + sem_post(&tlethr); + sem_post(&dispthr); + + /* 開始要 wait 了,莫名其妙 stop 的程式我最多只會送 + * 5 次 SIGCONT 給你(避免進入無限迴圈) + * 這種奇怪的程式就等著 SLE 吧 */ + for(i=0; i<=5; i++){ + wait4(pidcopy, &childexitstat, WUNTRACED, &toreturn->judge_rusage); + if(WIFSTOPPED(childexitstat) && i!=5){ + if(mcopt.flags & SCTMC_VERBOSE){ + printf("\n子程序因訊號 %d 而暫停,自動傳送 SIGCONT " + "(第 %d 次)\n", + WSTOPSIG(childexitstat), i+1); + } + kill(pidcopy, SIGCONT); + }else{ + if(WIFSIGNALED(childexitstat)){ + clock_gettime(CLOCK_REALTIME, &execfinish); + childterminated = 1; + toreturn->judge_signal = WTERMSIG(childexitstat); + if(toreturn->judge_signal == SIGXFSZ){ + toreturn->judge_result = SCTRES_OLE; + }else{ + toreturn->judge_result = SCTRES_RE; + } + toreturn->judge_exitcode = WEXITSTATUS(childexitstat); + break; + } + if(WIFEXITED(childexitstat)){ + clock_gettime(CLOCK_REALTIME, &execfinish); + childterminated = 1; + toreturn->judge_result = SCTRES_OK; + toreturn->judge_exitcode = WEXITSTATUS(childexitstat); + break; + } + } + } + if(!childterminated){ + kill(pidcopy, SIGKILL); + wait4(pidcopy, &childexitstat, WUNTRACED, &toreturn->judge_rusage); + toreturn->judge_result = SCTRES_SLE; + toreturn->judge_exitcode = WEXITSTATUS(childexitstat); + clock_gettime(CLOCK_REALTIME, &execfinish); + } + pthread_mutex_lock(&judge_tle_mx); + if(judge_tle && toreturn->judge_result == SCTRES_RE){ + toreturn->judge_result = SCTRES_TLE; + } + pthread_mutex_unlock(&judge_tle_mx); + difftimespec(&execstart, &execfinish, &toreturn->judge_time); + if(break_flag){ + toreturn->judge_result = SCTRES_AB; + } + sctjudge_makechild_cleanup_p1(); + if(mcopt.flags & SCTMC_VERBOSE){ + /* XXX 有更好的方法洗掉文字嗎? */ + fputs("\r \r" + , stdout); + } + }else{ + /* 子程序 + * 由此開始要很小心,因為我們是在 thread 裡面 fork (?) */ + close(childpipe[0]); + /* close-on-exec 用來關閉這個暫時的資料傳輸通道 */ + fcntl(childpipe[1], F_SETFD, fcntl(childpipe[1],F_GETFD) | FD_CLOEXEC); + + /* 設為獨立的 process group,不過失敗就算了,這不重要 */ + setpgid(getpid(), getpid()); + + /* 重設所有 signal handler + * XXX 我猜 signal 最大值是 32,還有其他更好的寫法嗎? */ + for(i=1; i<=32; i++){ + signal(i, SIG_DFL); + } + +#ifndef HAVE_CONF_CAP + disable_setuid(); +#endif + + fdinput = open(mcopt.inputfile, O_RDONLY); + if(fdinput < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_INPUT); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_INPUT); + } + if(dup2(fdinput, STDIN_FILENO) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_INPUT); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_INPUT); + } + + /* 注意:開啟輸出檔會直接覆寫現存檔案! */ + fdoutput = open(mcopt.outputfile, O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); + if(fdoutput < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_OUTPUT); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_OUTPUT); + } + if(dup2(fdoutput, STDOUT_FILENO) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_OUTPUT); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_OUTPUT); + } + +#ifndef HAVE_CONF_CAP + enable_setuid(); +#endif + fchown(fdoutput, procrealuid, -1); + + /* 再來就是 stderr 了 */ + if(mcopt.flags & SCTMC_REDIR_STDERR){ + if(dup2(fdoutput, STDERR_FILENO) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_STDERR); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_STDERR); + } + }else{ + fderr = open(WITH_NULL, O_RDONLY); + if(fderr < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_STDERR); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_STDERR); + } + if(dup2(fderr, STDERR_FILENO) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_STDERR); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_STDERR); + } + } + + /* 很暴力地把所有不該留著的 fd 關掉 */ + for(i=3; i<fdtablesize; i++){ + if(i != childpipe[1]){ + close(i); + } + } + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CLOSE_OTHERFD); + + /* 有人要求我要 chroot 嗎? */ + if(mcopt.chrootdir != NULL){ + if(chroot(mcopt.chrootdir) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_CHROOT); + exit(1); + }else{ + chdir("/"); + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CHROOT); + } + } + + +#ifdef HAVE_CONF_CAP + /* 確保修改身份的時候 capabilities 仍然留著 + * 等一下再自己把 capabilities 丟掉 */ + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); +#endif + + /* 我要 setgid 嗎? */ + if(mcopt.flags & SCTMC_SETGID){ + if(setregid(mcopt.gid, mcopt.gid) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETGID); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETGID); + } + if(setgroups(1, &mcopt.gid) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETGROUPS); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETGROUPS); + } + } + + /* 我要 setuid 嗎? */ + if(mcopt.flags & SCTMC_SETUID){ + if(setreuid(mcopt.uid, mcopt.uid) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETUID); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETUID); + } + } +#ifndef HAVE_CONF_CAP + else{ + /* 確保 setuid 可執行檔所造成的 effective UID 改變不會傳給子程序 */ + setuid(procrealuid); + } +#endif + + /* 開始設定資源限制 */ + if(mcopt.memlimit > 0){ + childressize = mcopt.memlimit; + childres.rlim_cur = childressize; + childres.rlim_max = childressize; + if(setrlimit(RLIMIT_AS, &childres) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_VM); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_VM); + } + } + + if(mcopt.outlimit > 0){ + childressize = mcopt.outlimit; + childres.rlim_cur = childressize; + childres.rlim_max = childressize; + if(setrlimit(RLIMIT_FSIZE, &childres) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_FSIZE); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_FSIZE); + } + } + + /* 從現在開始,不准再開檔案了 */ + childres.rlim_cur = 0; + childres.rlim_max = 0; + if(setrlimit( +#ifdef RLIMIT_NOFILE + RLIMIT_NOFILE +#else + RLIMIT_OFILE +#endif + , &childres) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_OPENFILE); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_OPENFILE); + } + + /* 從現在開始,不可以再產生新程序了 */ + if(setrlimit(RLIMIT_NPROC, &childres) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_PROC); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_PROC); + } + + + /* 非 Linux 系統就到此結束了,可以 exec 了 */ + +#ifdef HAVE_CONF_CAP + /* 正在丟掉 capabilities */ + cap_t nocap = cap_init(); + cap_clear_flag(nocap, CAP_EFFECTIVE); + cap_clear_flag(nocap, CAP_INHERITABLE); + cap_clear_flag(nocap, CAP_PERMITTED); + if(cap_set_proc(nocap) < 0){ + SCTCHILD_WRITE_FAILMSG(SCTCHILD_CAPSET); + exit(1); + }else{ + SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CAPSET); + } + /* 需要 drop bound 嗎?再考慮看看 */ +#endif + + if(mcopt.chrootdir == NULL){ + execl(mcopt.executable, mcopt.executable, NULL); + }else{ + if(mcopt.flags & SCTMC_NOCOPY){ + execl(mcopt.executable, mcopt.executable, NULL); + }else{ + execl(execshortname, execshortname, NULL); + } + } + + SCTCHILD_WRITE_FAILMSG(SCTCHILD_EXEC); + exit(2); + } + /* Judge 完成,準備離開 */ + fflush(stdout); + sctjudge_makechild_cleanup_p2(&mcopt, chrdir_oldmode, execdestname); + fflush(stdout); + (toreturn->mc_exitcode) = SCTMCRVAL_SUCCESS; + return (void*)toreturn; +} +#endif diff --git a/src/disptime.c b/src/ProcMonitor.c index 4f64cd7..05a0315 100644 --- a/src/disptime.c +++ b/src/ProcMonitor.c @@ -1,10 +1,10 @@ #ifdef HAVE_CONFIG_H -# include "config.h" +# include "SctConfig.h" #endif -#include "config2.h" -#include "common.h" -#include "sctcore.h" +#include "SctConst.h" +#include "SctCommon.h" +#include "ProcCommon.h" #include <stdio.h> #include <string.h> @@ -12,6 +12,12 @@ #include <pthread.h> #include <semaphore.h> + +void* sctproc_monitor(void* arg){ + return NULL; +} + +#if 0 void* sctjudge_displaytime(void* arg){ struct timespec timeinit, timecur, timepast; struct timespec timesleep; @@ -147,3 +153,4 @@ void* sctjudge_displaytime(void* arg){ return NULL; } +#endif diff --git a/src/common.c b/src/SctCommon.c index 6e7c550..27019e0 100644 --- a/src/common.c +++ b/src/SctCommon.c @@ -1,16 +1,92 @@ #ifdef HAVE_CONFIG_H -# include "config.h" +# include "SctConfig.h" #endif -#include "common.h" +#include "SctCommon.h" #include <time.h> +#include <errno.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> #include <unistd.h> #include <sys/types.h> +#include <pthread.h> + uid_t procrealuid = 0; uid_t proceffuid = 0; +int str_to_u(const char* s, unsigned* u){ + char* p; + unsigned o; + errno = 0; + o = strtoul(s, &p, 0); + if(errno || *p){ + return -1; + } + *u = o; + return 0; +} + +int read_complete(int fd, void* buf, size_t count){ + /* 一定要讀到 count 位元組的資料才會回傳,所以只要回傳 < count + * 就表示已經讀到 EOF 了 */ + char* bufp = (char*)buf; + size_t curcount = 0; + int rval; + do{ + rval = read(fd, bufp, count - curcount); + if(rval < 0){ + return rval; + }else if(rval == 0){ + return curcount; + }else{ + bufp += rval; + curcount += rval; + } + }while(curcount < count); + return count; +} + +int strerror_threadsafe(int errnum, char* buf, size_t buflen){ + static pthread_mutex_t strerror_call_mutex = PTHREAD_MUTEX_INITIALIZER; + char* rval; + int rlen; + pthread_mutex_lock(&strerror_call_mutex); + rval = strerror(errnum); + rlen = strlen(rval); + if(buflen < rlen){ + pthread_mutex_unlock(&strerror_call_mutex ); + return -1; + } + strcpy(buf, rval); + pthread_mutex_unlock(&strerror_call_mutex); + return 0; +} + +int sprintf_malloc(char** strstore, const char* format, ...){ + int len; + char* newstr; + va_list ap; + + va_start(ap, format); + len = vsnprintf(NULL, 0, format, ap) + 1; + va_end(ap); + + newstr = malloc(len); + if(newstr == NULL){ + return -1; + } + + va_start(ap, format); + len = vsnprintf(newstr, len, format, ap); + va_end(ap); + + *strstore = newstr; + return len; +} + void checktimespec(struct timespec* arg){ long tominus; if(arg->tv_nsec >= 1000000000){ diff --git a/src/SctCommon.h b/src/SctCommon.h new file mode 100644 index 0000000..a26d5ac --- /dev/null +++ b/src/SctCommon.h @@ -0,0 +1,88 @@ +#ifndef SCTJUDGE_SCT_COMMON +#define SCTJUDGE_SCT_COMMON + +#include <stdio.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> + +/* --- SctCommon.c --- */ + +/* 字串轉整數 wrapper */ +int str_to_u(const char*, unsigned*); + +/* 一定要讀到 count 位元組的資料才會回傳,所以只要回傳 < count + * 就表示已經讀到 EOF 了 */ +int read_complete(int fd, void* buf, size_t count); + + + +/* 安全的 strerror */ +int strerror_threadsafe(int, char*, size_t); + +/* 會自動配置空間的 sprintf */ +int sprintf_malloc(char**, const char*, ...); + + +/* 將某個 timespec 調整成合理的數值 */ +void checktimespec(struct timespec*); + +/* 計算前兩個 timespec 的數值差異,存到第三個參數 */ +void difftimespec(const struct timespec*, const struct timespec*, + struct timespec*); + +/* 比較兩個 timespec 的大小,使用方法與 strcmp 相同 */ +int comparetimespec(const struct timespec*, const struct timespec*); + + + +/* 這個一定要先執行,不然預設 uid 就是 0 + * 注意這函式只應該在 main() 一開始執行,不然就不知道最初的 uid 了! */ +void save_uids(void); + +#ifndef HAVE_CONF_CAP +void disable_setuid(void); +void enable_setuid(void); +#endif + +/* 避免 disable_setuid() 之後 getuid() 得到錯誤的值,所以用這些變數把最初的 + * UID 給記下來!*/ +extern uid_t procrealuid; +extern uid_t proceffuid; + + + + + +/* --- SctMain.c --- */ + +/* 如果不是 NULL 就 free 掉它 */ +#define FREE_IF_NOT_NULL(p) \ + if((p) != NULL){ \ + free(p); \ + } \ + (p) = NULL + +/* 如果是 NULL 就替換成 "(NULL)" */ +#define STRING_NULL(p) \ + ((p) == NULL) ? ("(NULL)") : (p) + +/* 測試 flag 是 yes 還是 no 並轉成字串 */ +#define DISPLAY_YESNO(flag, value) \ + (((flag) & (value)) ? ("Yes") : ("No")) + +/* sctjudge 的使用者界面選擇 */ +#define SCTJUDGE_UI_CLI 0 +#define SCTJUDGE_UI_TUI 1 +#define SCTJUDGE_UI_GUI 2 + +/* sctjudge 這個程式的 exit status + * 注意,檢測 malloc 失敗的東西已經去掉了 */ +#define SCTJUDGE_EXIT_SUCCESS 0 /* 當然就是正常結束 */ +#define SCTJUDGE_EXIT_SYNTAX 1 /* 解析階段:命令列語法錯誤 */ +#define SCTJUDGE_EXIT_INVALID 2 /* 配置無效或需要強制執行 */ +#define SCTJUDGE_EXIT_INSECURE 3 /* 不安全的設定值 */ +#define SCTJUDGE_EXIT_REJECT 4 /* 拒絕此參數或需要強制執行 */ +#define SCTJUDGE_EXIT_NA 255 /* 尚未實作的功能 */ + +#endif diff --git a/src/SctConst.h b/src/SctConst.h new file mode 100644 index 0000000..1a7a060 --- /dev/null +++ b/src/SctConst.h @@ -0,0 +1,11 @@ +/* 這個檔案用來存放人家所說的「程式內定」的常數 (hard coding) */ + +/* sctproc_checktime 每隔幾奈秒檢查一次時間 */ +#define SCTJUDGE_PROC_CHECKTIME_INTERVAL 128000000 + +/* sctproc_monitor 每個幾奈秒更新一次程序監視器 */ +#define SCTJUDGE_PROC_MONITOR_INTERVAL 384000000 + +/* sctproc_main 中,受測程式最多可以暫停幾次 */ +#define SCTJUDGE_PROC_MAX_STOP_TIMES 5 + diff --git a/src/SctMain.c b/src/SctMain.c new file mode 100644 index 0000000..05adc52 --- /dev/null +++ b/src/SctMain.c @@ -0,0 +1,645 @@ +#ifdef HAVE_CONFIG_H +# include "SctConfig.h" +#endif + +#include "SctConst.h" +#include "SctVersion.h" +#include "SctCommon.h" +#include "ProcCommon.h" +#include "JudgeCommon.h" +#include "CliCommon.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <locale.h> +#include <time.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/types.h> + +#include <pthread.h> +#include <semaphore.h> + +#ifdef HAVE_CONF_UGIDNAME +#include <grp.h> +#include <pwd.h> +#endif + +#include <l4darr.h> +#include <l4arg.h> + +#define SCTJUDGE_CLIARG_CHECK_MISSING \ + if(++i >= argc){ \ + fprintf(stderr, "%s: 選項「%s」之後需要一個參數\n", \ + argv[0], argv[i-1]); \ + exit(SCTJUDGE_EXIT_SYNTAX); \ + } + +#define SCTJUDGE_CLIARG_CHECK_QARG_MISSING \ + if(!l4qarg_hasvalue(qarglist[j])){ \ + fprintf(stderr, "%s: 在「%s」之後缺少一個值\n", \ + argv[0], qarglist[j].arg_name); \ + exit(SCTJUDGE_EXIT_SYNTAX); \ + } + +#define SCTJUDGE_CLIARG_CHECK_QARG_EXTRA \ + if(l4qarg_hasvalue(qarglist[j])){ \ + fprintf(stderr, "%s: 在「%s」之後並不需要參數\n", \ + argv[0], qarglist[j].arg_name); \ + exit(SCTJUDGE_EXIT_SYNTAX); \ + } \ + +typedef struct misc_option_flag_and_number { + char* id; + unsigned* num; + unsigned flag; +} MISCOPT; + +int main(int argc, char* argv[]){ + /* 照理說是 zh_TW.utf8,但考慮作業系統差異還是從環境變數抓 + * (反正出錯是使用者的事) */ + setlocale(LC_ALL, ""); + + int i, j, k; + int verbose = 0; /* 0 是安靜模式(機器模式)、1 是 verbose、2 是 debug */ + int ui = SCTJUDGE_UI_CLI; + _Bool parseok = 0; + char* tmpstr; + JUDGEINFO sjopt; /* 無設定檔模式下,傳送這個給 CliMain() */ + L4QARG* qarglist; + +#ifdef HAVE_CONF_UGIDNAME + struct passwd* pwinfo; + struct group* grinfo; +#endif + + save_uids(); + +#ifndef HAVE_CONF_CAP + /* 即使有 setuid root,還是不能讓每個使用者拿到 root 權限 + * 所以說只有在 fork 的時候才要把這個權限開起來,其他就關掉吧 */ + disable_setuid(); +#endif + + /* 定義 -m 時的選項清單 + * 當然有些特殊的像是 errfile 和 uid/gid 以及 arg 這裡沒辦法 */ + const MISCOPT miscopt[] = { + {"time", &(sjopt.procinfo.i_limit_time), 0}, + {"timelimit", &(sjopt.procinfo.i_limit_time), 0}, + {"time-limit", &(sjopt.procinfo.i_limit_time), 0}, + {"tle", &(sjopt.procinfo.i_limit_time), 0}, + + {"stderr", NULL, PROCINFO_FLAG_REDIR_STDERR}, + {"redir-stderr", NULL, PROCINFO_FLAG_REDIR_STDERR}, + + {"secure", NULL, PROCINFO_FLAG_SECURE_CHECK}, + {"secure-check", NULL, PROCINFO_FLAG_SECURE_CHECK}, + + {"mem", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + {"memory", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + {"ram", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + {"as", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + {"addr-space", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + {"address-space", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + {"vm", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + {"virtual-memory", &(sjopt.procinfo.i_limit_mem), PROCINFO_FLAG_LIMIT_MEM}, + + {"core", &(sjopt.procinfo.i_limit_core), PROCINFO_FLAG_LIMIT_CORE}, + {"coredump", &(sjopt.procinfo.i_limit_core), PROCINFO_FLAG_LIMIT_CORE}, + {"corefile", &(sjopt.procinfo.i_limit_core), PROCINFO_FLAG_LIMIT_CORE}, + + {"cputime", &(sjopt.procinfo.i_limit_cpu), PROCINFO_FLAG_LIMIT_CPU}, + {"cputime-limit", &(sjopt.procinfo.i_limit_cpu), PROCINFO_FLAG_LIMIT_CPU}, + + {"outlimit", &(sjopt.procinfo.i_limit_out), PROCINFO_FLAG_LIMIT_OUT}, + {"out-limit", &(sjopt.procinfo.i_limit_out), PROCINFO_FLAG_LIMIT_OUT}, + {"output-limit", &(sjopt.procinfo.i_limit_out), PROCINFO_FLAG_LIMIT_OUT}, + + {"openfile", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE}, + {"fdlimit", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE}, + {"fdmax", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE}, + {"filelimit", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE}, + {"file-limit", &(sjopt.procinfo.i_limit_file), PROCINFO_FLAG_LIMIT_FILE}, + + {"process", &(sjopt.procinfo.i_limit_proc), PROCINFO_FLAG_LIMIT_PROC}, + {"proc", &(sjopt.procinfo.i_limit_proc), PROCINFO_FLAG_LIMIT_PROC}, + {"proc-limit", &(sjopt.procinfo.i_limit_proc), PROCINFO_FLAG_LIMIT_PROC}, + + {"stack", &(sjopt.procinfo.i_limit_stack), PROCINFO_FLAG_LIMIT_STACK}, + {"stack-size", &(sjopt.procinfo.i_limit_stack), PROCINFO_FLAG_LIMIT_STACK}, + {"stack-limit", &(sjopt.procinfo.i_limit_stack), PROCINFO_FLAG_LIMIT_STACK} + + }; + + + + /* 將無設定檔模式的結構設定為預設值 */ + sctjudge_setdefault(&sjopt); + sjopt.procinfo.i_argv = l4da_create(sizeof(char*), 2); + tmpstr = NULL; + l4da_pushback(sjopt.procinfo.i_argv, &tmpstr); + + /* 解析命令列的時間到了 + * 為了方便與安全起見,所有 sjopt 裡面的字串都會用 strdup() 複製一次 */ + for(i=1; i<argc; i++){ + if(argv[i][0] == '-'){ + if(!strcmp(&argv[i][1], "help") || !strcmp(&argv[i][1], "-help")){ + printf(SCTJUDGE_TITLEBAR"\n\n" + "用法:%s [選項]\n" + "選項:\n\n" + " -v/-verbose\n" + " 顯示更多訊息(第二個 -v 可以顯示所有重要指令執行過" + "程)\n\n" + " -V/version\n" + " 顯示版本資訊並查看可使用的額外功能列表\n\n" + " -n/-dryrun\n" + " 只列出設定值而不要執行(包含 -v)\n\n" + " -f/-force\n" + " 忽略警告訊息並強制執行\n\n" + " -t/-time <時間>\n" + " 受測程式時間限制為<時間>毫秒\n\n" + " -e/-exec <檔案>\n" + " 指定受測程式可執行檔名稱(務必靜態連結!)\n\n" + " -i/-input <檔案>\n" + " 指定要導向至受測程式標準輸入的檔案,若未指定則為 " + WITH_NULL "\n\n" + " -o/-output <檔案>\n" + " 指定要導向至受測程式標準輸出的檔案,若未指定則為 " + WITH_NULL "\n\n" + " -r/-root <目錄>\n" + " 受測程式將以<目錄>為根目錄執行,若無此選項則關" + "閉 chroot 功能\n\n" + " -m/-misc 選項1[=值],選項2[=值],選項3[=值]...\n" + " 設定額外參數:\n\n" + " mem=<大小>\n" + " 設定受測程式記憶體上限為<大小>位元組\n\n" + " outlimit=<大小>\n" + " 受測程式最多只能輸出<大小>位元組,若未指定則不" + "限制\n" + " (無限制時請確定有足夠的磁碟空間!)\n\n" + " stderr\n" + " 將受測程式的標準錯誤也導向至輸出檔。若未指定," + "則只有標準輸\n" + " 出會寫入輸出檔案,標準錯誤則被導向至 " + WITH_NULL "\n\n" + " nocopy\n" + " 如果啟用了 chroot 功能,預設情況下本程式會自動" + "將檔案複製\n" + " 到新的根目錄中,並在結束時自動刪除檔案。\n" + " 使用此選項則取消自動複製的功能,但請注意:此時" + " file 指定的\n" + " 檔案名稱是相對於新的根目錄而不是現在的根目錄!" + "\n\n" + " uid=<UID>\n" + " 受測程式將以 UID 為 <UID> 的使用者身份執行\n\n" + " gid=<GID>\n" + " 受測程式將以 GID 為 <GID> 的群組身份執行\n" + " 此選項會同時設定 real/effective/supplementary " + "GID(s)\n" + " 原有的 supplementry GIDs 會被清空,只剩下這裡指" + "定的數值\n\n" + , argv[0]); + return SCTJUDGE_EXIT_SUCCESS; + + }else if(!strcmp(&argv[i][1], "V") || + !strcmp(&argv[i][1], "version") || + !strcmp(&argv[i][1], "-version")){ + fputs(SCTJUDGE_ABOUT_STRING, stdout); + exit(SCTJUDGE_EXIT_SUCCESS); + + }else if(!strcmp(&argv[i][1], "v") || + !strcmp(&argv[i][1], "verbose")){ + verbose++; + sjopt.procinfo.i_verbose = verbose; + + }else if(!strcmp(&argv[i][1], "d") || + !strcmp(&argv[i][1], "debug")){ + verbose += 2; + sjopt.procinfo.i_verbose = verbose; + + }else if(!strcmp(&argv[i][1], "q") || + !strcmp(&argv[i][1], "quiet") || + !strcmp(&argv[i][1], "silent")){ + verbose = 0; + sjopt.procinfo.i_verbose = verbose; + + }else if(!strcmp(&argv[i][1], "n") || + !strcmp(&argv[i][1], "dryrun")){ + verbose = (verbose < 1) ? 1 : verbose; + sjopt.procinfo.i_flag |= PROCINFO_FLAG_DRYRUN; + + }else if(!strcmp(&argv[i][1], "f") || + !strcmp(&argv[i][1], "force")){ + sjopt.procinfo.i_flag |= PROCINFO_FLAG_FORCE; + + }else if(!strcmp(&argv[i][1], "t") || + !strcmp(&argv[i][1], "time")){ + SCTJUDGE_CLIARG_CHECK_MISSING; + if(str_to_u(argv[i], &sjopt.procinfo.i_limit_time) < 0){ + fprintf(stderr, "%s: 「%s」不是正確的時間設定值\n", + argv[0], argv[i]); + exit(SCTJUDGE_EXIT_SYNTAX); + } + + }else if(!strcmp(&argv[i][1], "e") || + !strcmp(&argv[i][1], "exec")){ + SCTJUDGE_CLIARG_CHECK_MISSING; + FREE_IF_NOT_NULL(sjopt.procinfo.i_exec); + sjopt.procinfo.i_exec = strdup(argv[i]); + + }else if(!strcmp(&argv[i][1], "i") || + !strcmp(&argv[i][1], "in") || + !strcmp(&argv[i][1], "input")){ + SCTJUDGE_CLIARG_CHECK_MISSING; + FREE_IF_NOT_NULL(sjopt.procinfo.i_infile); + sjopt.procinfo.i_infile = strdup(argv[i]); + + }else if(!strcmp(&argv[i][1], "o") || + !strcmp(&argv[i][1], "out") || + !strcmp(&argv[i][1], "output")){ + SCTJUDGE_CLIARG_CHECK_MISSING; + FREE_IF_NOT_NULL(sjopt.procinfo.i_outfile); + sjopt.procinfo.i_outfile = strdup(argv[i]); + + }else if(!strcmp(&argv[i][1], "r") || + !strcmp(&argv[i][1], "root") || + !strcmp(&argv[i][1], "chroot")){ + SCTJUDGE_CLIARG_CHECK_MISSING; + FREE_IF_NOT_NULL(sjopt.procinfo.i_chroot); + sjopt.procinfo.i_chroot = strdup(argv[i]); + + }else if(!strcmp(&argv[i][1], "u") || + !strcmp(&argv[i][1], "ui") || + !strcmp(&argv[i][1], "interface")){ + SCTJUDGE_CLIARG_CHECK_MISSING; + if(!strcmp(argv[i], "c") || + !strcmp(argv[i], "cli") || + !strcmp(argv[i], "cmd") || + !strcmp(argv[i], "cmdln") || + !strcmp(argv[i], "command") || + !strcmp(argv[i], "commandline")){ + ui = SCTJUDGE_UI_CLI; + }else if(!strcmp(argv[i], "t") || + !strcmp(argv[i], "tui") || + !strcmp(argv[i], "text") || + !strcmp(argv[i], "term") || + !strcmp(argv[i], "terminal") || + !strcmp(argv[i], "curses") || + !strcmp(argv[i], "ncurses")){ + ui = SCTJUDGE_UI_TUI; + }else if(!strcmp(argv[i], "g") || + !strcmp(argv[i], "gui") || + !strcmp(argv[i], "graph") || + !strcmp(argv[i], "graphic") || + !strcmp(argv[i], "graphics") || + !strcmp(argv[i], "graphical") || + !strcmp(argv[i], "x") || + !strcmp(argv[i], "xorg") || + !strcmp(argv[i], "xwin") || + !strcmp(argv[i], "xwindow")){ + ui = SCTJUDGE_UI_GUI; + }else{ + fprintf(stderr, "%s: 不明的使用者界面「%s」\n", + argv[0], argv[i]); + exit(SCTJUDGE_EXIT_SYNTAX); + } + + }else if(!strcmp(&argv[i][1], "m") || + !strcmp(&argv[i][1], "misc") || + !strcmp(&argv[i][1], "opt") || + !strcmp(&argv[i][1], "option")){ + SCTJUDGE_CLIARG_CHECK_MISSING; + qarglist = l4qarg_parse(argv[i]); + for(j=0; !l4qarg_end(qarglist[j]); j++){ + parseok = 0; + for(k=0; k < sizeof(miscopt) / sizeof(MISCOPT); k++){ + if(!strcmp(miscopt[k].id, qarglist[j].arg_name)){ + if(miscopt[k].num == NULL){ + SCTJUDGE_CLIARG_CHECK_QARG_EXTRA; + sjopt.procinfo.i_flag |= miscopt[k].flag; + parseok = 1; + break; + }else{ + SCTJUDGE_CLIARG_CHECK_QARG_MISSING; + sjopt.procinfo.i_flag |= miscopt[k].flag; + if(str_to_u( + qarglist[j].arg_value, miscopt[k].num) < 0) + { + fprintf(stderr, "%s: 「%s」並不是整數\n", + argv[0], qarglist[j].arg_value); + exit(SCTJUDGE_EXIT_SYNTAX); + } + parseok = 1; + break; + } + } + + /* 這裡是有加上 no 的選項 */ + if(strlen(qarglist[j].arg_name) >= 2 && + !strncmp(qarglist[j].arg_name, "no", 2) && + !strcmp((qarglist[j].arg_name)+2, miscopt[k].id)){ + SCTJUDGE_CLIARG_CHECK_QARG_EXTRA; + sjopt.procinfo.i_flag &= ~(miscopt[k].flag); + parseok = 1; + break; + } + + } + + if(parseok){ + continue; + } + + /* copy/nocopy 設在 JUDGEINFO 但不在 PROCINFO */ + if(!strcmp(qarglist[j].arg_name, "copy")){ + SCTJUDGE_CLIARG_CHECK_QARG_EXTRA; + sjopt.flag |= JUDGEINFO_FLAG_COPY; + }else if(!strcmp(qarglist[j].arg_name, "nocopy")){ + SCTJUDGE_CLIARG_CHECK_QARG_EXTRA; + sjopt.flag &= ~(JUDGEINFO_FLAG_COPY); + } + + /* 修改 uid / gid 的功能 */ + else if(!strcmp(qarglist[j].arg_name, "uid") || + !strcmp(qarglist[j].arg_name, "user")){ + SCTJUDGE_CLIARG_CHECK_QARG_MISSING; + sjopt.procinfo.i_flag |= PROCINFO_FLAG_SETUID; + if(str_to_u( + qarglist[j].arg_value, &sjopt.procinfo.i_uid) < 0){ +#ifdef HAVE_CONF_UGIDNAME + pwinfo = getpwnam(qarglist[j].arg_value); + if(pwinfo == NULL){ + fprintf(stderr, "%s: 「%s」不是正確的 UID 或" + "使用者名稱\n", + argv[0], qarglist[j].arg_value); + exit(SCTJUDGE_EXIT_SYNTAX); + } + sjopt.procinfo.i_uid = pwinfo->pw_uid; +#else + fprintf(stderr, "%s: 「%s」並不是整數\n", + argv[0], qarglist[j].arg_value); + exit(SCTJUDGE_EXIT_SYNTAX); +#endif + } + }else if(!strcmp(qarglist[j].arg_name, "gid") || + !strcmp(qarglist[j].arg_name, "group")){ + SCTJUDGE_CLIARG_CHECK_QARG_MISSING; + sjopt.procinfo.i_flag |= PROCINFO_FLAG_SETGID; + if(str_to_u( + qarglist[j].arg_value, &sjopt.procinfo.i_gid) < 0){ +#ifdef HAVE_CONF_UGIDNAME + grinfo = getgrnam(qarglist[j].arg_value); + if(grinfo == NULL){ + fprintf(stderr, "%s: 「%s」不是正確的 GID 或" + "群組名稱\n", + argv[0], qarglist[j].arg_value); + exit(SCTJUDGE_EXIT_SYNTAX); + } + sjopt.procinfo.i_gid = grinfo->gr_gid; +#else + fprintf(stderr, "%s: 「%s」並不是整數\n", + argv[0], qarglist[j].arg_value); + exit(SCTJUDGE_EXIT_SYNTAX); +#endif + } + } + + /* stderr 也可以指定個別的檔案 */ + else if(!strcmp(qarglist[j].arg_name, "err") || + !strcmp(qarglist[j].arg_name, "errfile")){ + SCTJUDGE_CLIARG_CHECK_QARG_MISSING; + FREE_IF_NOT_NULL(sjopt.procinfo.i_errfile); + sjopt.procinfo.i_errfile = + strdup(qarglist[j].arg_value); + } + + /* 可以指定程式執行時的參數 */ + else if(!strcmp(qarglist[j].arg_name, "arg") || + !strcmp(qarglist[j].arg_name, "argv") || + !strcmp(qarglist[j].arg_name, "addarg") || + !strcmp(qarglist[j].arg_name, "appendarg")){ + SCTJUDGE_CLIARG_CHECK_QARG_MISSING; + tmpstr = strdup(qarglist[j].arg_value); + l4da_pushback(sjopt.procinfo.i_argv, &tmpstr); + }else if(!strcmp(qarglist[j].arg_name, "arg0") || + !strcmp(qarglist[j].arg_name, "argv0")){ + SCTJUDGE_CLIARG_CHECK_QARG_MISSING; + tmpstr = strdup(qarglist[j].arg_value); + l4da_v(sjopt.procinfo.i_argv, char*, 0) = tmpstr; + } + else{ + fprintf(stderr, "%s: 「%s」是不明的選項\n", argv[0], + qarglist[j].arg_name); + exit(SCTJUDGE_EXIT_SYNTAX); + } + } + l4qarg_free(qarglist); + }else{ + fprintf(stderr, "%s: 不明的選項「%s」\n" + "請嘗試執行 %s -help 來取得說明\n" + , argv[0], argv[i], argv[0]); + return SCTJUDGE_EXIT_SYNTAX; + } + }else{ + fprintf(stderr, "%s: 抱歉,設定檔功能尚未實作\n", argv[0]); + return SCTJUDGE_EXIT_NA; + } + } + + if(l4da_v(sjopt.procinfo.i_argv, char*, 0) == NULL){ + /* 未明確指定 argv[0] 則直接將執行檔名稱複製過去 */ + if(sjopt.procinfo.i_exec != NULL){ + l4da_v(sjopt.procinfo.i_argv, char*, 0) = + strdup(sjopt.procinfo.i_exec); + } + } + + /* argv 尾端必須是 NULL */ + tmpstr = NULL; + l4da_pushback(sjopt.procinfo.i_argv, &tmpstr); + + /* 至此可以進入主程式了 */ + if(verbose >= 1){ + puts(SCTJUDGE_TITLEBAR"\n"); + } + + switch(ui){ + case SCTJUDGE_UI_CLI: + if(verbose >= 2){ + printf("DEBUG: %s: Call sctcli_main() [Entering CLI mode]\n", + __func__); + } + sctcli_main(NULL, &sjopt); + break; + + case SCTJUDGE_UI_TUI: + fprintf(stderr, "%s: 抱歉,curses 界面尚未實作\n", argv[0]); + exit(SCTJUDGE_EXIT_NA); + break; + + case SCTJUDGE_UI_GUI: + fprintf(stderr, "%s: 抱歉,圖形化使用者界面尚未實作\n", argv[0]); + exit(SCTJUDGE_EXIT_NA); + break; + + default: + fprintf(stderr, "%s: internal error: invalid ui %d\n", + argv[0], ui); + exit(SCTJUDGE_EXIT_NA); + } + + sctjudge_freestring(&sjopt); + + /********************************************************************** + * 以下都要清掉!!! + * ********************************************************************/ + +#if 0 + /* 檢查與修正設定值 */ + if(verbose){ + puts("正在檢查設定值是否正確......"); + } + if(mcopt.executable == NULL){ + fputs("受測程式可執行檔名稱為必要參數,不可為空白\n", stderr); + exit(SCTEXIT_TOOFEW); + } + if(mcopt.inputfile == NULL){ + mcopt.inputfile = WITH_NULL; + } + if(mcopt.outputfile == NULL){ + fputs("輸出檔案名稱必要參數,不可為空白\n", stderr); + exit(SCTEXIT_TOOFEW); + } + if(mcopt.exectime <= 0){ + fputs("執行時間限制為必要參數,不可為空白!\n", stderr); + exit(SCTEXIT_TOOFEW); + } + if((mcopt.flags & SCTMC_SETUID)){ + if(mcopt.uid == 0){ + if(!force){ + fputs("將 UID 設為 0 並不安全!(加上 -f 來強制執行)\n", + stderr); + exit(SCTEXIT_BADID); + }else{ + if(procrealuid != 0){ + fputs("只有 root 可以將 UID 設定 0\n", stderr); + exit(SCTEXIT_BADID); + } + } + } + }else{ + if(procrealuid == 0 && !force){ + fputs("不允許以 root 身份執行本程式(加上 -f 來強制執行)\n", + stderr); + exit(SCTEXIT_BADID); + } + } + + /* 現在開始建立 thread 了!*/ + pthread_t tmain; /* 其他 thread 的 ID 放在全域變數 */ + struct makechildrval* mtreturn; /* 接收 sctjudge_makechild 的回傳值 */ + + /* 傳給另外兩個 thread 的參數 */ + int exectimelimit = mcopt.exectime; + /* XXX 抱歉 hard coding,我以後會改進 */ + long displayinterval = SCT_DISPTIME_INTERVAL; + + pthread_attr_t detstate, joistate; + /* 除了主要的 sctjudge_makechild 以外, + * 我想其他的 thread 沒有 join 的必要 */ + pthread_attr_init(&detstate); + pthread_attr_setdetachstate(&detstate, PTHREAD_CREATE_DETACHED); + pthread_attr_init(&joistate); + pthread_attr_setdetachstate(&joistate, PTHREAD_CREATE_JOINABLE); + pthread_mutex_init(&pidmutex, NULL); + pthread_mutex_init(&tkill_mx, NULL); + pthread_mutex_init(&tdisplay_mx, NULL); + pthread_mutex_init(&judge_tle_mx, NULL); + sem_init(&mcthr, 0, 0); + sem_init(&tlethr, 0, 0); + sem_init(&dispthr, 0, 0); + pthread_create(&tmain, &joistate, &sctjudge_makechild, (void*)&mcopt); + if(!dryrun){ + pthread_create(&tkill, &detstate, &sctjudge_checktle, + (void*)&exectimelimit); + } + if(!dryrun && verbose){ + pthread_create(&tdisplay, &detstate, &sctjudge_displaytime, + (void*)&displayinterval); + } + pthread_attr_destroy(&detstate); + pthread_attr_destroy(&joistate); + sem_post(&mcthr); + + pthread_join(tmain, (void**)&mtreturn); + /* XXX 底下這訊息很有可能讓使用者覺得很奇怪 */ + pthread_mutex_destroy(&pidmutex); + pthread_mutex_destroy(&tkill_mx); + pthread_mutex_destroy(&tdisplay_mx); + + sem_destroy(&mcthr); + sem_destroy(&tlethr); + sem_destroy(&dispthr); + + if(dryrun){ + return SCTEXIT_SUCCESS; + } + + /* 這裡就要顯示結果了 */ + if(mtreturn == NULL || mtreturn->mc_exitcode != 0){ + fprintf(stderr, "%s: 因錯誤發生而結束程式\n", argv[0]); + free((void*)mtreturn); + sem_destroy(&mcthr); + exit(SCTEXIT_THREAD); + }else{ + if(verbose){ + putchar('\r'); + puts("======================================================="); + fputs("評測結果:", stdout); + if(mtreturn->judge_result < 0 || + mtreturn->judge_result >= SCTRES_UD){ + mtreturn->judge_result = SCTRES_UD; + } + printf("%s (%s)\n", + sctres_text[0][mtreturn->judge_result], + sctres_text[1][mtreturn->judge_result]); + printf("執行時間:%ld.%03ld 秒\n", + mtreturn->judge_time.tv_sec, + mtreturn->judge_time.tv_nsec / 1000000); + printf("程式離開狀態:%d\n", mtreturn->judge_exitcode); + if(mtreturn->judge_result == SCTRES_RE){ + printf("程式因訊號 %d 而終止\n", mtreturn->judge_signal); + } + puts("-------------------------------------------------------"); + printf("User CPU time: %ld.%03ld\n" + "System CPU time: %ld.%03ld\n", + mtreturn->judge_rusage.ru_utime.tv_sec, + mtreturn->judge_rusage.ru_utime.tv_usec / 1000, + mtreturn->judge_rusage.ru_stime.tv_sec, + mtreturn->judge_rusage.ru_stime.tv_usec / 1000); + puts("======================================================="); + }else{ + /* 輸出格式: + * 第一行是結果代碼、時間、離開狀態 + * 第二行是收到的訊號 (只在 RE 時會有) */ + if(mtreturn->judge_result < 0 || + mtreturn->judge_result >= SCTRES_UD){ + mtreturn->judge_result = SCTRES_UD; + } + fputs(sctres_text[0][mtreturn->judge_result], stdout); + putchar(' '); + printf("%ld%03ld %d\n", mtreturn->judge_time.tv_sec, + mtreturn->judge_time.tv_nsec / 1000000, + mtreturn->judge_exitcode); + if(mtreturn->judge_result == SCTRES_RE){ + printf("%d\n", mtreturn->judge_signal); + } + } + free((void*)mtreturn); + } + +#endif + return SCTJUDGE_EXIT_SUCCESS; +} diff --git a/src/version.h.in b/src/SctVersion.h.in index 7d03bc4..7d03bc4 100644 --- a/src/version.h.in +++ b/src/SctVersion.h.in diff --git a/src/common.h b/src/common.h deleted file mode 100644 index 5d45377..0000000 --- a/src/common.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef SCTJUDGE_COMMON_FUNCTIONS -#define SCTJUDGE_COMMON_FUNCTIONS - -#include <time.h> -#include <unistd.h> -#include <sys/types.h> - -void checktimespec(struct timespec*); -void difftimespec(const struct timespec*, const struct timespec*, - struct timespec*); -int comparetimespec(const struct timespec*, const struct timespec*); -void save_uids(void); /* 這個一定要先執行,不然預設 uid 就是 0 */ - -#ifndef HAVE_CONF_CAP -void disable_setuid(void); -void enable_setuid(void); -#endif - -/* 避免 disable_setuid() 之後 getuid() 得到錯誤的值,所以用這些變數把最初的 - * UID 給記下來!*/ -extern uid_t procrealuid; -extern uid_t proceffuid; - -#endif diff --git a/src/config2.h b/src/config2.h deleted file mode 100644 index 41ae605..0000000 --- a/src/config2.h +++ /dev/null @@ -1,14 +0,0 @@ -/* 你沒有看錯,真的有 config2.h - * 這個檔案用來存放人家所說的「程式內定」的常數 (hard coding) */ - -/* 丟給 nanosleep 用的 */ -#define SCT_CHECKTLE_INTERVAL 128000000 -#define SCT_DISPTIME_INTERVAL 384000000 - -/* sctjudge 這個程式的 exit status */ -#define SCTEXIT_SUCCESS 0 /* 當然就是正常結束 */ -#define SCTEXIT_SYNTAX 1 /* 解析階段:命令列語法錯誤 */ -#define SCTEXIT_TOOFEW 2 /* 檢查階段:少了一些必須的選項 */ -#define SCTEXIT_BADID 3 /* 檢查階段:錯誤或不允許的 uid/gid */ -#define SCTEXIT_THREAD 4 /* 實作階段:thread 回傳錯誤 */ -#define SCTEXIT_MALLOC 5 /* 配置記憶體發生錯誤,這個可能發生在程式任何地方 */ diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 3b48b18..0000000 --- a/src/main.c +++ /dev/null @@ -1,456 +0,0 @@ -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include "config2.h" -#include "version.h" -#include "common.h" -#include "sctcore.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <locale.h> -#include <time.h> -#include <grp.h> -#include <pwd.h> -#include <sys/time.h> -#include <sys/resource.h> -#include <sys/types.h> - -#include <pthread.h> -#include <semaphore.h> - -#include <l4darr.h> -#include <l4arg.h> - -#define SCTMAIN_ERRNOARG "%s: 選項「%s」之後需要一個參數\n" -#define SCTMAIN_ERRNOQARG "%s: 在「%s」之後缺少一個值\n" -#define SCTMAIN_ERREXTRAQARG "%s: 在「%s」之後並不需要參數\n" - -#define SCTMAIN_CHECKARGEXIST \ - do{ \ - i++; \ - if(i >= argc){ \ - fprintf(stderr, SCTMAIN_ERRNOARG, argv[0], argv[i-1]); \ - exit(SCTEXIT_SYNTAX); \ - } \ - }while(0) - -#define SCTMAIN_CHECKQARGHAVEVALUE \ - do{ \ - if(!l4qarg_hasvalue(qarglist[j])){ \ - fprintf(stderr, SCTMAIN_ERRNOQARG, \ - argv[0], qarglist[j].arg_name); \ - exit(SCTEXIT_SYNTAX); \ - } \ - }while(0) - -#define SCTMAIN_CHECKQARGNOVALUE \ - do{ \ - if(l4qarg_hasvalue(qarglist[j])){ \ - fprintf(stderr, SCTMAIN_ERREXTRAQARG, \ - argv[0], qarglist[j].arg_name); \ - exit(SCTEXIT_SYNTAX); \ - } \ - }while(0) - -static const char* sctres_text[2][SCTRES_MAX] = { - {"OK", "RE", "TLE", "OLE", "SLE", "AB", "UD"}, - { - "正常結束", - "執行過程中發生錯誤", - "超過時間限制", - "輸出超過限制", - "暫停次數太多", - "使用者中斷操作", - "未定義的結果" - } -}; - -int main(int argc, char* argv[]){ - /* 照理說是 zh_TW.utf8,但考慮作業系統差異還是從環境變數抓 - * (反正出錯是使用者的事) */ - setlocale(LC_ALL, ""); - - int i, j; - struct makechildopt mcopt; /* 送給 sctjudge_makechild 當參數用 */ - char verbose=0; /* 0 是安靜模式(機器模式)、1 是 verbose、2 是 debug */ - char dryrun=0, force=0; - L4QARG* qarglist; -#ifdef HAVE_CONF_UGIDNAME - struct passwd* pwinfo; - struct group* grinfo; -#endif - - save_uids(); - -#ifndef HAVE_CONF_CAP - /* 即使有 setuid root,還是不能讓每個使用者拿到 root 權限 - * 所以說只有在 fork 的時候才要把這個權限開起來,其他就關掉吧 */ - disable_setuid(); -#endif - - /* 預設值當然都是 0 */ - memset(&mcopt, 0 ,sizeof(mcopt)); - /* 據說有些 NULL 不是 0,所以還是設定一下比較保險 */ - mcopt.executable = NULL; - mcopt.chrootdir = NULL; - mcopt.inputfile = NULL; - mcopt.outputfile = NULL; - - /* 解析命令列的時間到了 */ - for(i=1; i<argc; i++){ - if(argv[i][0] == '-'){ - if(!strcmp(&argv[i][1], "help") || !strcmp(&argv[i][1], "-help")){ - printf(SCTJUDGE_TITLEBAR"\n\n" - "用法:%s [選項]\n" - "選項:\n\n" - " -v/-verbose\n" - " 顯示更多訊息(第二個 -v 可以顯示所有重要指令執行過" - "程)\n\n" - " -V/version\n" - " 顯示版本資訊並查看可使用的額外功能列表\n\n" - " -n/-dryrun\n" - " 只列出設定值而不要執行(包含 -v)\n\n" - " -f/-force\n" - " 忽略警告訊息並強制執行\n\n" - " -t/-time <時間>\n" - " 受測程式時間限制為<時間>毫秒\n\n" - " -e/-exec <檔案>\n" - " 指定受測程式可執行檔名稱(務必靜態連結!)\n\n" - " -i/-input <檔案>\n" - " 指定要導向至受測程式標準輸入的檔案,若未指定則為 " - WITH_NULL"\n\n" - " -o/-output <檔案>\n" - " 指定要導向至受測程式標準輸出的檔案,若未指定則為 " - WITH_NULL"\n\n" - " -r/-root <目錄>\n" - " 受測程式將以<目錄>為根目錄執行,若無此選項則關" - "閉 chroot 功能\n\n" - " -m/-misc 選項1[=值],選項2[=值],選項3[=值]...\n" - " 設定額外參數:\n\n" - " mem=<大小>\n" - " 設定受測程式記憶體上限為<大小>位元組\n\n" - " outlimit=<大小>\n" - " 受測程式最多只能輸出<大小>位元組,若未指定則不" - "限制\n" - " (無限制時請確定有足夠的磁碟空間!)\n\n" - " stderr\n" - " 將受測程式的標準錯誤也導向至輸出檔。若未指定," - "則只有標準輸\n" - " 出會寫入輸出檔案,標準錯誤則被導向至 " - WITH_NULL"\n\n" - " nocopy\n" - " 如果啟用了 chroot 功能,預設情況下本程式會自動" - "將檔案複製\n" - " 到新的根目錄中,並在結束時自動刪除檔案。\n" - " 使用此選項則取消自動複製的功能,但請注意:此時" - " file 指定的\n" - " 檔案名稱是相對於新的根目錄而不是現在的根目錄!" - "\n\n" - " uid=<UID>\n" - " 受測程式將以 UID 為 <UID> 的使用者身份執行\n\n" - " gid=<GID>\n" - " 受測程式將以 GID 為 <GID> 的群組身份執行\n" - " 此選項會同時設定 real/effective/supplementary " - "GID(s)\n" - " 原有的 supplementry GIDs 會被清空,只剩下這裡指" - "定的數值\n\n" - , argv[0]); - return SCTEXIT_SUCCESS; - }else if(!strcmp(&argv[i][1], "V") || - !strcmp(&argv[i][1], "version") || - !strcmp(&argv[i][1], "-version")){ - fputs(SCTJUDGE_ABOUT_STRING, stdout); - exit(SCTEXIT_SUCCESS); - }else if(!strcmp(&argv[i][1], "v") || - !strcmp(&argv[i][1], "verbose")){ - verbose++; - mcopt.flags |= SCTMC_VERBOSE; - if(verbose >= 2){ - mcopt.flags |= SCTMC_DEBUG; - } - }else if(!strcmp(&argv[i][1], "n") || - !strcmp(&argv[i][1], "dryrun")){ - dryrun = 1, verbose = 1; - mcopt.flags |= (SCTMC_DRYRUN | SCTMC_VERBOSE); - }else if(!strcmp(&argv[i][1], "f") || - !strcmp(&argv[i][1], "force")){ - force = 1; - mcopt.flags |= (SCTMC_FORCE); - }else if(!strcmp(&argv[i][1], "t") || - !strcmp(&argv[i][1], "time")){ - SCTMAIN_CHECKARGEXIST; - if(sscanf(argv[i], "%u", &mcopt.exectime) <= 0 || - mcopt.exectime <= 0){ - fprintf(stderr, "%s: 「%s」不是正確的時間設定值\n", - argv[0], argv[i]); - exit(SCTEXIT_SYNTAX); - } - }else if(!strcmp(&argv[i][1], "e") || - !strcmp(&argv[i][1], "exec")){ - SCTMAIN_CHECKARGEXIST; - mcopt.executable = argv[i]; - }else if(!strcmp(&argv[i][1], "i") || - !strcmp(&argv[i][1], "in") || - !strcmp(&argv[i][1], "input")){ - SCTMAIN_CHECKARGEXIST; - mcopt.inputfile = argv[i]; - }else if(!strcmp(&argv[i][1], "o") || - !strcmp(&argv[i][1], "out") || - !strcmp(&argv[i][1], "output")){ - SCTMAIN_CHECKARGEXIST; - mcopt.outputfile = argv[i]; - }else if(!strcmp(&argv[i][1], "r") || - !strcmp(&argv[i][1], "root") || - !strcmp(&argv[i][1], "chroot")){ - SCTMAIN_CHECKARGEXIST; - mcopt.chrootdir = argv[i]; - }else if(!strcmp(&argv[i][1], "m") || - !strcmp(&argv[i][1], "misc") || - !strcmp(&argv[i][1], "opt") || - !strcmp(&argv[i][1], "option")){ - SCTMAIN_CHECKARGEXIST; - qarglist = l4qarg_parse(argv[i]); - if(qarglist == NULL){ - fprintf(stderr, "%s: 記憶體配置發生錯誤\n", argv[0]); - exit(SCTEXIT_MALLOC); - } - for(j=0; !l4qarg_end(qarglist[j]); j++){ - if(!strcmp(qarglist[j].arg_name, "mem") || - !strcmp(qarglist[j].arg_name, "memory")){ - SCTMAIN_CHECKQARGHAVEVALUE; - if(sscanf(qarglist[j].arg_value, "%u", - &mcopt.memlimit) <= 0 || - mcopt.memlimit <= 0){ - fprintf(stderr, "%s: 「%s」不是正確的記憶體限制" - "設定值\n", argv[0], - qarglist[j].arg_value); - exit(SCTEXIT_SYNTAX); - } - }else if(!strcmp(qarglist[j].arg_name, "outlimit") || - !strcmp(qarglist[j].arg_name, "outlim")){ - SCTMAIN_CHECKQARGHAVEVALUE; - if(sscanf(qarglist[j].arg_value, "%u", - &mcopt.outlimit) <= 0 || - mcopt.outlimit <= 0){ - fprintf(stderr, "%s: 「%s」不是正確的輸出限制" - "設定\n", argv[0], qarglist[j].arg_value); - exit(SCTEXIT_SYNTAX); - } - }else if(!strcmp(qarglist[j].arg_name, "stderr")){ - SCTMAIN_CHECKQARGNOVALUE; - mcopt.flags |= SCTMC_REDIR_STDERR; - }else if(!strcmp(qarglist[j].arg_name, "nocopy")){ - SCTMAIN_CHECKQARGNOVALUE; - mcopt.flags |= SCTMC_NOCOPY; - }else if(!strcmp(qarglist[j].arg_name, "uid") || - !strcmp(qarglist[j].arg_name, "user")){ - SCTMAIN_CHECKQARGHAVEVALUE; - mcopt.flags |= SCTMC_SETUID; - if(sscanf(qarglist[j].arg_value, "%u", &mcopt.uid) - <= 0){ -#ifdef HAVE_CONF_UGIDNAME - pwinfo = getpwnam(qarglist[j].arg_value); - if(pwinfo == NULL){ - fprintf(stderr, "%s: 「%s」不是正確的 UID 或" - "使用者名稱\n", - argv[0], qarglist[j].arg_value); - exit(SCTEXIT_SYNTAX); - } - mcopt.uid = pwinfo->pw_uid; -#else - fprintf(stderr, "%s: 「%s」並不是整數\n", - argv[0], qarglist[j].arg_value); - exit(SCTEXIT_SYNTAX); -#endif - } - }else if(!strcmp(qarglist[j].arg_name, "gid") || - !strcmp(qarglist[j].arg_name, "group")){ - SCTMAIN_CHECKQARGHAVEVALUE; - mcopt.flags |= SCTMC_SETGID; - if(sscanf(qarglist[j].arg_value, "%u", &mcopt.gid) - <= 0){ -#ifdef HAVE_CONF_UGIDNAME - grinfo = getgrnam(qarglist[j].arg_value); - if(grinfo == NULL){ - fprintf(stderr, "%s: 「%s」不是正確的 GID 或" - "群組名稱\n", - argv[0], qarglist[j].arg_value); - exit(SCTEXIT_SYNTAX); - } - mcopt.gid = grinfo->gr_gid; -#else - fprintf(stderr, "%s: 「%s」並不是整數\n", - argv[0], qarglist[j].arg_value); - exit(SCTEXIT_SYNTAX); -#endif - } - }else{ - fprintf(stderr, "%s: 「%s」是不明的選項\n", argv[0], - qarglist[j].arg_name); - exit(SCTEXIT_SYNTAX); - } - } - l4qarg_free(qarglist); - }else{ - fprintf(stderr, "%s: 不明的選項「%s」\n", argv[0], argv[i]); - return SCTEXIT_SYNTAX; - } - }else{ - fprintf(stderr, "%s: 參數 %d「%s」是多餘的\n" - "請嘗試執行 %s -help 來取得說明\n" - , argv[0], i, argv[i], argv[0]); - return SCTEXIT_SYNTAX; - } - } - /* 至此可以進入主程式了 */ - if(verbose){ - puts(SCTJUDGE_TITLEBAR"\n"); - } - - /* 檢查與修正設定值 */ - if(verbose){ - puts("正在檢查設定值是否正確......"); - } - if(mcopt.executable == NULL){ - fputs("受測程式可執行檔名稱為必要參數,不可為空白\n", stderr); - exit(SCTEXIT_TOOFEW); - } - if(mcopt.inputfile == NULL){ - mcopt.inputfile = WITH_NULL; - } - if(mcopt.outputfile == NULL){ - fputs("輸出檔案名稱必要參數,不可為空白\n", stderr); - exit(SCTEXIT_TOOFEW); - } - if(mcopt.exectime <= 0){ - fputs("執行時間限制為必要參數,不可為空白!\n", stderr); - exit(SCTEXIT_TOOFEW); - } - if((mcopt.flags & SCTMC_SETUID)){ - if(mcopt.uid == 0){ - if(!force){ - fputs("將 UID 設為 0 並不安全!(加上 -f 來強制執行)\n", - stderr); - exit(SCTEXIT_BADID); - }else{ - if(procrealuid != 0){ - fputs("只有 root 可以將 UID 設定 0\n", stderr); - exit(SCTEXIT_BADID); - } - } - } - }else{ - if(procrealuid == 0 && !force){ - fputs("不允許以 root 身份執行本程式(加上 -f 來強制執行)\n", - stderr); - exit(SCTEXIT_BADID); - } - } - - /* 現在開始建立 thread 了!*/ - pthread_t tmain; /* 其他 thread 的 ID 放在全域變數 */ - struct makechildrval* mtreturn; /* 接收 sctjudge_makechild 的回傳值 */ - - /* 傳給另外兩個 thread 的參數 */ - int exectimelimit = mcopt.exectime; - /* XXX 抱歉 hard coding,我以後會改進 */ - long displayinterval = SCT_DISPTIME_INTERVAL; - - pthread_attr_t detstate, joistate; - /* 除了主要的 sctjudge_makechild 以外, - * 我想其他的 thread 沒有 join 的必要 */ - pthread_attr_init(&detstate); - pthread_attr_setdetachstate(&detstate, PTHREAD_CREATE_DETACHED); - pthread_attr_init(&joistate); - pthread_attr_setdetachstate(&joistate, PTHREAD_CREATE_JOINABLE); - pthread_mutex_init(&pidmutex, NULL); - pthread_mutex_init(&tkill_mx, NULL); - pthread_mutex_init(&tdisplay_mx, NULL); - pthread_mutex_init(&judge_tle_mx, NULL); - sem_init(&mcthr, 0, 0); - sem_init(&tlethr, 0, 0); - sem_init(&dispthr, 0, 0); - pthread_create(&tmain, &joistate, &sctjudge_makechild, (void*)&mcopt); - if(!dryrun){ - pthread_create(&tkill, &detstate, &sctjudge_checktle, - (void*)&exectimelimit); - } - if(!dryrun && verbose){ - pthread_create(&tdisplay, &detstate, &sctjudge_displaytime, - (void*)&displayinterval); - } - pthread_attr_destroy(&detstate); - pthread_attr_destroy(&joistate); - sem_post(&mcthr); - - pthread_join(tmain, (void**)&mtreturn); - /* XXX 底下這訊息很有可能讓使用者覺得很奇怪 */ - pthread_mutex_destroy(&pidmutex); - pthread_mutex_destroy(&tkill_mx); - pthread_mutex_destroy(&tdisplay_mx); - - sem_destroy(&mcthr); - sem_destroy(&tlethr); - sem_destroy(&dispthr); - - if(dryrun){ - return SCTEXIT_SUCCESS; - } - - /* 這裡就要顯示結果了 */ - if(mtreturn == NULL || mtreturn->mc_exitcode != 0){ - fprintf(stderr, "%s: 因錯誤發生而結束程式\n", argv[0]); - free((void*)mtreturn); - sem_destroy(&mcthr); - exit(SCTEXIT_THREAD); - }else{ - if(verbose){ - putchar('\r'); - puts("======================================================="); - fputs("評測結果:", stdout); - if(mtreturn->judge_result < 0 || - mtreturn->judge_result >= SCTRES_UD){ - mtreturn->judge_result = SCTRES_UD; - } - printf("%s (%s)\n", - sctres_text[0][mtreturn->judge_result], - sctres_text[1][mtreturn->judge_result]); - printf("執行時間:%ld.%03ld 秒\n", - mtreturn->judge_time.tv_sec, - mtreturn->judge_time.tv_nsec / 1000000); - printf("程式離開狀態:%d\n", mtreturn->judge_exitcode); - if(mtreturn->judge_result == SCTRES_RE){ - printf("程式因訊號 %d 而終止\n", mtreturn->judge_signal); - } - puts("-------------------------------------------------------"); - printf("User CPU time: %ld.%03ld\n" - "System CPU time: %ld.%03ld\n", - mtreturn->judge_rusage.ru_utime.tv_sec, - mtreturn->judge_rusage.ru_utime.tv_usec / 1000, - mtreturn->judge_rusage.ru_stime.tv_sec, - mtreturn->judge_rusage.ru_stime.tv_usec / 1000); - puts("======================================================="); - }else{ - /* 輸出格式: - * 第一行是結果代碼、時間、離開狀態 - * 第二行是收到的訊號 (只在 RE 時會有) */ - if(mtreturn->judge_result < 0 || - mtreturn->judge_result >= SCTRES_UD){ - mtreturn->judge_result = SCTRES_UD; - } - fputs(sctres_text[0][mtreturn->judge_result], stdout); - putchar(' '); - printf("%ld%03ld %d\n", mtreturn->judge_time.tv_sec, - mtreturn->judge_time.tv_nsec / 1000000, - mtreturn->judge_exitcode); - if(mtreturn->judge_result == SCTRES_RE){ - printf("%d\n", mtreturn->judge_signal); - } - } - free((void*)mtreturn); - } - return SCTEXIT_SUCCESS; -} diff --git a/src/mkchild.c b/src/mkchild.c deleted file mode 100644 index 805105f..0000000 --- a/src/mkchild.c +++ /dev/null @@ -1,711 +0,0 @@ -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include "config2.h" -#include "common.h" -#include "sctcore.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <signal.h> -#include <fcntl.h> -#include <dirent.h> -#include <unistd.h> -#include <errno.h> -#include <grp.h> -#include <time.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <sys/resource.h> -#include <sys/time.h> -#include <sys/wait.h> - -#include <pthread.h> -#include <semaphore.h> - -#ifdef HAVE_CONF_CAP -#include <sys/prctl.h> -#include <sys/capability.h> -#endif - -#define abort_makechild(val) \ - do { \ - sctjudge_makechild_cleanup(&mcopt, chrdir_oldmode, execdestname); \ - toreturn->mc_exitcode = (val); \ - pthread_exit((void*)toreturn); \ - }while(0) - -#define SCTCHILD_OPEN_INPUT 0 -#define SCTCHILD_DUP2_INPUT 1 -#define SCTCHILD_OPEN_OUTPUT 2 -#define SCTCHILD_DUP2_OUTPUT 3 -#define SCTCHILD_OPEN_STDERR 4 -#define SCTCHILD_DUP2_STDERR 5 -#define SCTCHILD_CLOSE_OTHERFD 6 -#define SCTCHILD_SETRLIMIT_VM 7 -#define SCTCHILD_SETRLIMIT_FSIZE 8 -#define SCTCHILD_SETRLIMIT_OPENFILE 9 -#define SCTCHILD_SETRLIMIT_PROC 10 -#define SCTCHILD_CHROOT 11 -#define SCTCHILD_SETUID 12 -#define SCTCHILD_SETGID 13 -#define SCTCHILD_SETGROUPS 14 -#define SCTCHILD_CAPSET 15 -#define SCTCHILD_EXEC 16 -#define SCTCHILD_MSGMAX 17 /* 訊息代碼的最大值 */ -#define SCTCHILD_WRITE_SUCCMSG(reason) \ - do{ \ - childmsg[0] = (reason); \ - childmsg[1] = 0; \ - write(childpipe[1], (void*)childmsg, sizeof(int)*2); \ - }while(0) -#define SCTCHILD_WRITE_FAILMSG(reason) \ - do{ \ - childmsg[0] = (reason); \ - childmsg[1] = errno; \ - write(childpipe[1], (void*)childmsg, sizeof(int)*2); \ - }while(0) - -pid_t pidchild; -pthread_mutex_t pidmutex, tkill_mx, tdisplay_mx, judge_tle_mx; -pthread_t tkill, tdisplay; -char tkill_yes = 0, tdisplay_yes = 0, judge_tle = 0; -sem_t mcthr, tlethr, dispthr; - -static const char* childmsg_text[SCTCHILD_MSGMAX] = { - "開啟輸入檔案", - "重新導向標準輸入", - "開啟輸出檔案", - "重新導向標準輸出", - "開啟用於導向標準錯誤的檔案", - "重新導向標準錯誤", - "關閉所有不使用的檔案", - "設定記憶體限制", - "設定輸出限制", - "設定禁止開啟其他檔案", - "設定禁止產生新程序", - "執行 chroot", - "修改 real 和 effective UID", - "修改 real 和 effective GID", - "修改 supplementary GIDs", - "丟棄所有 Linux capabilities", - "執行受測程式" -}; - -static void sctjudge_makechild_cleanup_p1(void){ - pthread_mutex_lock(&tkill_mx); - if(tkill_yes){ - tkill_yes = 0; - pthread_mutex_unlock(&tkill_mx); - sem_post(&tlethr); - }else{ - pthread_mutex_unlock(&tkill_mx); - } - - pthread_mutex_lock(&tdisplay_mx); - if(tdisplay_yes){ - tdisplay_yes = 0; - pthread_mutex_unlock(&tdisplay_mx); - sem_post(&dispthr); - }else{ - pthread_mutex_unlock(&tdisplay_mx); - } - -} - -static void sctjudge_makechild_cleanup_p2(mcopt, oldperm, copiedexe) - const struct makechildopt* mcopt; - const mode_t oldperm; - char* copiedexe; -{ - if(oldperm != -1 && mcopt->chrootdir != NULL){ - if(mcopt->flags & SCTMC_VERBOSE){ - printf("恢復 %s 的權限\n", mcopt->chrootdir); - } - chmod(mcopt->chrootdir, oldperm); - } - if(copiedexe != NULL && mcopt->chrootdir != NULL && - !(mcopt->flags & SCTMC_NOCOPY)){ - if(mcopt->flags & SCTMC_VERBOSE){ - printf("移除 %s......\n", copiedexe); - } - unlink(copiedexe); - free(copiedexe); - } -} - -static void sctjudge_makechild_cleanup(mcopt, oldperm, copiedexe) - const struct makechildopt* mcopt; - const mode_t oldperm; - char* copiedexe; -{ - sctjudge_makechild_cleanup_p1(); - sctjudge_makechild_cleanup_p2(mcopt, oldperm, copiedexe); -} - -static int copyfilebyname(const char* src, const char* destdir - , char** srcsn, char** destfn){ - /* strerror 並不 thread safe,因此必須確定只有一個 thread 會用到 */ - /* 為了確保安全,要求目的地目錄必須是空的 */ - DIR* dirp; - if((dirp=opendir(destdir)) == NULL){ - fprintf(stderr, "無法開啟目錄 %s:%s\n", destdir, strerror(errno)); - return -1; - } - struct dirent* entryp; - while((entryp=readdir(dirp)) != NULL){ - if(entryp->d_name[0] != '.' || - (entryp->d_name[1] != '.' && entryp->d_name[1] != '\0')){ - fprintf(stderr, "拒絕複製檔案:目錄 %s 不是空的\n", destdir); - return -1; - } - } - int fd, fd2; - if((fd=open(src, O_RDONLY)) < 0){ - fprintf(stderr, "無法讀取檔案 %s:%s\n", src, strerror(errno)); - return -1; - } - const int bufsize = 4096; - void* buf = malloc(bufsize); - char* srcshortname; - char* tmp; - if((tmp=strrchr(src, '/')) == NULL){ - srcshortname = (char*)src; - }else{ - srcshortname = tmp + 1; - } - *srcsn = srcshortname; - char* destfilename = (char*) - malloc(strlen(srcshortname) + strlen(destdir) + 1); - sprintf(destfilename, "%s/%s", destdir, srcshortname); - *destfn = destfilename; - /* 新檔案權限是 0755,umask 亂弄的我可不管 */ - if((fd2=open(destfilename, O_CREAT | O_WRONLY | O_EXCL, - S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) < 0){ - fprintf(stderr, "無法建立檔案 %s:%s\n", - destfilename, strerror(errno)); - free(buf); - return -1; - } - int readcount; - while((readcount = read(fd, buf, bufsize)) > 0){ - write(fd2, buf, readcount); - } - close(fd); - close(fd2); - free(buf); - return 0; -} - -static void sctjudge_list_settings(const struct makechildopt* mcopt){ - printf("\t受測程式可執行檔名稱:%s", mcopt->executable); - if(mcopt->flags & SCTMC_NOCOPY && mcopt->chrootdir != NULL){ - printf(" (相對於 %s)\n" - "\t受測程式執行時的根目錄:%s\n" - "\t執行前複製檔案:否\n" - , mcopt->chrootdir, mcopt->chrootdir); - }else{ - putchar('\n'); - if(mcopt->chrootdir != NULL){ - printf("\t受測程式執行時的根目錄:%s\n" - "\t執行前複製檔案:是\n" - , mcopt->chrootdir); - }else{ - puts("\t受測程式執行時的根目錄:/\n" - "\t執行前複製檔案:否"); - } - } - printf("\t輸入檔案:%s\n" - "\t輸出檔案:%s" - , mcopt->inputfile, mcopt->outputfile); - if(mcopt->flags & SCTMC_REDIR_STDERR){ - puts(" (重新導向標準輸出和標準錯誤)"); - }else{ - puts(" (重新導向標準輸出)"); - } - printf("\t執行時間限制:%d 毫秒\n", mcopt->exectime); - fputs("\t記憶體使用量限制:", stdout); - if(mcopt->memlimit > 0){ - printf("%d 位元組\n", mcopt->memlimit); - }else{ - puts("無限制"); - } - fputs("\t輸出限制:", stdout); - if(mcopt->outlimit > 0){ - printf("%d 位元組\n", mcopt->outlimit); - }else{ - puts("無限制"); - } - if(mcopt->flags & SCTMC_SETUID){ - printf("\t設定受測程式執行時的 UID 為 %u\n", mcopt->uid); - }else{ - puts("\t執行受測程式時維持原有的 real UID 和 effective UID"); - } - if(mcopt->flags & SCTMC_SETGID){ - printf("\t設定受測程式執行時的 GID 為 %u\n", mcopt->gid); - }else{ - puts("\t執行受測程式時維持原有的 real GID、effective GID " - "和 supplementary GID"); - } -} - -static int read_size(int fd, void* buf, size_t count){ - /* 一定要讀到 count 位元組的資料才會回傳,所以只要回傳 < count - * 就表示已經讀到 EOF 了 */ - char* bufp = (char*)buf; - size_t curcount = 0; - int rval; - do{ - rval = read(fd, bufp, count - curcount); - if(rval < 0){ - return rval; - }else if(rval == 0){ - return curcount; - }else{ - bufp += rval; - curcount += rval; - } - }while(curcount < count); - return count; -} - -void* sctjudge_makechild(void* arg){ - /* 首先等所有 thread 都啟動完成再開始動作 */ - sem_wait(&mcthr); - - /* 宣告變數囉! */ - int i; - - /* 函式參數與回傳值,Judge 結果就當作回傳值 */ - struct makechildopt mcopt = *(struct makechildopt*)arg; - struct makechildrval* toreturn = - (struct makechildrval*)malloc(sizeof(struct makechildrval)); - memset(toreturn, 0, sizeof(struct makechildrval)); - - /* 可執行檔名稱儲存區 */ - char* execshortname = NULL; /* 可以說就是 basename,chroot+copy 才會用 */ - char* execdestname = NULL; /* 複製出來的暫時執行檔,chroot+copy 才會用 */ - /* 這幾個變數用來儲存 Judge 結果,如果沒有啟用 verbose - * 且一切正常,那這些是唯一會輸出的東西了,目的是輸出容 - * 易被其他程式分析 */ - - /* 要 chroot 的目錄的相關資訊 */ - struct stat chrdirinfo; /* 取得原始的 mode 用的 */ - mode_t chrdir_oldmode = -1; /* 這樣我們才能在結束時 chmod 回去 */ - - /* 子程序的 PID,因為不會變,所以複製一份下來就不用去全域變數拿的 */ - pid_t pidcopy; - - /* 和子程序溝通用的 */ - int childpipe[2]; - int childmsg[2]; /* 每個訊息的大小都是 sizeof(int)*2 - [0]=發生事情的名稱 [1]=errno */ - int readsize = sizeof(int) * 2; - - /* 計時用的 */ - struct timespec execstart, execfinish; - - /* 子程序結束狀態 */ - int childexitstat; - char childterminated = 0; - - /* 子程序才用的到的變數 */ - int fdinput, fdoutput, fderr, childressize; - const int fdtablesize = getdtablesize(); - struct rlimit childres; - - /* 變數宣告完了 */ - - if(mcopt.flags & SCTMC_VERBOSE){ - puts("列出設定值:"); - sctjudge_list_settings(&mcopt); - fflush(stdout); - } - if(mcopt.flags & SCTMC_DRYRUN){ - abort_makechild(SCTMCRVAL_SUCCESS); - } - - /* 我們要開始做正事了 */ - /* 由此開始,我是唯一會用到 strerror() 的 thread - * 所以說應該沒有問題 */ - if(mcopt.chrootdir != NULL && !(mcopt.flags & SCTMC_NOCOPY)){ - /* 必須要複製可執行檔 */ - if(mcopt.flags & SCTMC_VERBOSE){ - puts("開始複製可執行檔......"); - } - if(copyfilebyname(mcopt.executable, mcopt.chrootdir, &execshortname - , &execdestname) < 0){ - abort_makechild(SCTMCRVAL_PREPARE); - } - } - /* 再來是 chroot 的問題,避免受測程式亂開目錄之類的, - * 所以把權限設成 0555,不過如果沒有變更 uid 和 gid , - * 受測程式還是可以 chmod 回來 */ - if(mcopt.chrootdir != NULL){ - if(mcopt.flags & SCTMC_VERBOSE){ - puts("取得用於 chroot 的目錄相關資訊"); - } - if(stat(mcopt.chrootdir, &chrdirinfo) < 0){ - fprintf(stderr, "無法取得目錄 %s 的相關資訊:%s\n", - mcopt.chrootdir, strerror(errno)); - abort_makechild(SCTMCRVAL_PREPARE); - }else{ - if((chrdirinfo.st_mode & S_IFMT) == S_IFDIR){ - chrdir_oldmode = chrdirinfo.st_mode & - (S_ISUID |S_ISGID |S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO); - }else{ - fprintf(stderr, "%s 並不是目錄\n", mcopt.chrootdir); - abort_makechild(SCTMCRVAL_PREPARE); - } - } - if(mcopt.flags & SCTMC_VERBOSE){ - puts("嘗試變更此目錄的權限"); - } - if(chmod(mcopt.chrootdir, - S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0){ - fprintf(stderr, "無法變更目錄 %s 的權限\n", mcopt.chrootdir); - abort_makechild(SCTMCRVAL_PREPARE); - } - } - - fflush(stdout); -#ifndef HAVE_CONF_CAP - enable_setuid(); -#endif - /* 建立用來和 chlid process 溝通的 pipe */ - pipe(childpipe); - pthread_mutex_lock(&pidmutex); -#ifdef HAVE_WORKING_FORK - pidchild = fork(); -#else -#error "This program requires a working fork function." -#endif - if(pidchild < 0){ -#ifndef HAVE_CONF_CAP - disable_setuid(); -#endif - pthread_mutex_unlock(&pidmutex); - fprintf(stderr, "子程序建立失敗:%s\n", strerror(errno)); - abort_makechild(SCTMCRVAL_FORK); - } - else if(pidchild > 0){ - /* 我們原本的 thread */ - /* 從這裡開始,我們需要 setuid 來 kill 子程序, - * 所以說不要執行 disable_setuid() */ - pidcopy = pidchild; - pthread_mutex_unlock(&pidmutex); - close(childpipe[1]); - /* 從 pipe 接收 child process 狀態,每次固定要讀 sizeof(int)*2 */ - while(read_size(childpipe[0], (void*)childmsg, readsize) == readsize){ - if(childmsg[0] < 0 || childmsg[0] >= SCTCHILD_MSGMAX){ - /* 會發生嗎?除非被亂七八糟 ptrace 吧 - * 這時候先砍掉子程序再說 */ - kill(pidcopy, SIGKILL); - close(childpipe[0]); - abort_makechild(SCTMCRVAL_INVALID); - }else{ - if(childmsg[1]){ - fprintf(stderr, "子程序[%d]發生錯誤:%s:%s\n", - pidcopy, childmsg_text[childmsg[0]], - strerror(childmsg[1])); - kill(pidcopy, SIGKILL); - close(childpipe[0]); - abort_makechild(SCTMCRVAL_CHILDFAIL); - }else{ - if(mcopt.flags & SCTMC_VERBOSE){ - printf("子程序[%d]:%s\n", pidcopy, - childmsg_text[childmsg[0]]); - } - } - } - } - clock_gettime(CLOCK_REALTIME, &execstart); - close(childpipe[0]); - - /* 現在我們確定受測程式已經在執行了,所以需要記錄一下時間 - * 通知其他 thread */ - sem_post(&tlethr); - sem_post(&dispthr); - - /* 開始要 wait 了,莫名其妙 stop 的程式我最多只會送 - * 5 次 SIGCONT 給你(避免進入無限迴圈) - * 這種奇怪的程式就等著 SLE 吧 */ - for(i=0; i<=5; i++){ - wait4(pidcopy, &childexitstat, WUNTRACED, &toreturn->judge_rusage); - if(WIFSTOPPED(childexitstat) && i!=5){ - if(mcopt.flags & SCTMC_VERBOSE){ - printf("\n子程序因訊號 %d 而暫停,自動傳送 SIGCONT " - "(第 %d 次)\n", - WSTOPSIG(childexitstat), i+1); - } - kill(pidcopy, SIGCONT); - }else{ - if(WIFSIGNALED(childexitstat)){ - clock_gettime(CLOCK_REALTIME, &execfinish); - childterminated = 1; - toreturn->judge_signal = WTERMSIG(childexitstat); - if(toreturn->judge_signal == SIGXFSZ){ - toreturn->judge_result = SCTRES_OLE; - }else{ - toreturn->judge_result = SCTRES_RE; - } - toreturn->judge_exitcode = WEXITSTATUS(childexitstat); - break; - } - if(WIFEXITED(childexitstat)){ - clock_gettime(CLOCK_REALTIME, &execfinish); - childterminated = 1; - toreturn->judge_result = SCTRES_OK; - toreturn->judge_exitcode = WEXITSTATUS(childexitstat); - break; - } - } - } - if(!childterminated){ - kill(pidcopy, SIGKILL); - wait4(pidcopy, &childexitstat, WUNTRACED, &toreturn->judge_rusage); - toreturn->judge_result = SCTRES_SLE; - toreturn->judge_exitcode = WEXITSTATUS(childexitstat); - clock_gettime(CLOCK_REALTIME, &execfinish); - } - pthread_mutex_lock(&judge_tle_mx); - if(judge_tle && toreturn->judge_result == SCTRES_RE){ - toreturn->judge_result = SCTRES_TLE; - } - pthread_mutex_unlock(&judge_tle_mx); - difftimespec(&execstart, &execfinish, &toreturn->judge_time); - if(break_flag){ - toreturn->judge_result = SCTRES_AB; - } - sctjudge_makechild_cleanup_p1(); - if(mcopt.flags & SCTMC_VERBOSE){ - /* XXX 有更好的方法洗掉文字嗎? */ - fputs("\r \r" - , stdout); - } - }else{ - /* 子程序 - * 由此開始要很小心,因為我們是在 thread 裡面 fork (?) */ - close(childpipe[0]); - /* close-on-exec 用來關閉這個暫時的資料傳輸通道 */ - fcntl(childpipe[1], F_SETFD, fcntl(childpipe[1],F_GETFD) | FD_CLOEXEC); - - /* 設為獨立的 process group,不過失敗就算了,這不重要 */ - setpgid(getpid(), getpid()); - - /* 重設所有 signal handler - * XXX 我猜 signal 最大值是 32,還有其他更好的寫法嗎? */ - for(i=1; i<=32; i++){ - signal(i, SIG_DFL); - } - -#ifndef HAVE_CONF_CAP - disable_setuid(); -#endif - - fdinput = open(mcopt.inputfile, O_RDONLY); - if(fdinput < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_INPUT); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_INPUT); - } - if(dup2(fdinput, STDIN_FILENO) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_INPUT); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_INPUT); - } - - /* 注意:開啟輸出檔會直接覆寫現存檔案! */ - fdoutput = open(mcopt.outputfile, O_WRONLY | O_CREAT | O_TRUNC, - S_IRUSR | S_IWUSR); - if(fdoutput < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_OUTPUT); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_OUTPUT); - } - if(dup2(fdoutput, STDOUT_FILENO) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_OUTPUT); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_OUTPUT); - } - -#ifndef HAVE_CONF_CAP - enable_setuid(); -#endif - fchown(fdoutput, procrealuid, -1); - - /* 再來就是 stderr 了 */ - if(mcopt.flags & SCTMC_REDIR_STDERR){ - if(dup2(fdoutput, STDERR_FILENO) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_STDERR); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_STDERR); - } - }else{ - fderr = open(WITH_NULL, O_RDONLY); - if(fderr < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_OPEN_STDERR); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_OPEN_STDERR); - } - if(dup2(fderr, STDERR_FILENO) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_DUP2_STDERR); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_DUP2_STDERR); - } - } - - /* 很暴力地把所有不該留著的 fd 關掉 */ - for(i=3; i<fdtablesize; i++){ - if(i != childpipe[1]){ - close(i); - } - } - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CLOSE_OTHERFD); - - /* 有人要求我要 chroot 嗎? */ - if(mcopt.chrootdir != NULL){ - if(chroot(mcopt.chrootdir) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_CHROOT); - exit(1); - }else{ - chdir("/"); - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CHROOT); - } - } - - -#ifdef HAVE_CONF_CAP - /* 確保修改身份的時候 capabilities 仍然留著 - * 等一下再自己把 capabilities 丟掉 */ - prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); -#endif - - /* 我要 setgid 嗎? */ - if(mcopt.flags & SCTMC_SETGID){ - if(setregid(mcopt.gid, mcopt.gid) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETGID); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETGID); - } - if(setgroups(1, &mcopt.gid) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETGROUPS); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETGROUPS); - } - } - - /* 我要 setuid 嗎? */ - if(mcopt.flags & SCTMC_SETUID){ - if(setreuid(mcopt.uid, mcopt.uid) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETUID); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETUID); - } - } -#ifndef HAVE_CONF_CAP - else{ - /* 確保 setuid 可執行檔所造成的 effective UID 改變不會傳給子程序 */ - setuid(procrealuid); - } -#endif - - /* 開始設定資源限制 */ - if(mcopt.memlimit > 0){ - childressize = mcopt.memlimit; - childres.rlim_cur = childressize; - childres.rlim_max = childressize; - if(setrlimit(RLIMIT_AS, &childres) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_VM); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_VM); - } - } - - if(mcopt.outlimit > 0){ - childressize = mcopt.outlimit; - childres.rlim_cur = childressize; - childres.rlim_max = childressize; - if(setrlimit(RLIMIT_FSIZE, &childres) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_FSIZE); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_FSIZE); - } - } - - /* 從現在開始,不准再開檔案了 */ - childres.rlim_cur = 0; - childres.rlim_max = 0; - if(setrlimit( -#ifdef RLIMIT_NOFILE - RLIMIT_NOFILE -#else - RLIMIT_OFILE -#endif - , &childres) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_OPENFILE); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_OPENFILE); - } - - /* 從現在開始,不可以再產生新程序了 */ - if(setrlimit(RLIMIT_NPROC, &childres) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_SETRLIMIT_PROC); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_SETRLIMIT_PROC); - } - - - /* 非 Linux 系統就到此結束了,可以 exec 了 */ - -#ifdef HAVE_CONF_CAP - /* 正在丟掉 capabilities */ - cap_t nocap = cap_init(); - cap_clear_flag(nocap, CAP_EFFECTIVE); - cap_clear_flag(nocap, CAP_INHERITABLE); - cap_clear_flag(nocap, CAP_PERMITTED); - if(cap_set_proc(nocap) < 0){ - SCTCHILD_WRITE_FAILMSG(SCTCHILD_CAPSET); - exit(1); - }else{ - SCTCHILD_WRITE_SUCCMSG(SCTCHILD_CAPSET); - } - /* 需要 drop bound 嗎?再考慮看看 */ -#endif - - if(mcopt.chrootdir == NULL){ - execl(mcopt.executable, mcopt.executable, NULL); - }else{ - if(mcopt.flags & SCTMC_NOCOPY){ - execl(mcopt.executable, mcopt.executable, NULL); - }else{ - execl(execshortname, execshortname, NULL); - } - } - - SCTCHILD_WRITE_FAILMSG(SCTCHILD_EXEC); - exit(2); - } - /* Judge 完成,準備離開 */ - fflush(stdout); - sctjudge_makechild_cleanup_p2(&mcopt, chrdir_oldmode, execdestname); - fflush(stdout); - (toreturn->mc_exitcode) = SCTMCRVAL_SUCCESS; - return (void*)toreturn; -} diff --git a/src/sctcore.h b/src/sctcore.h deleted file mode 100644 index 13c8783..0000000 --- a/src/sctcore.h +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef SCTJUDGE_CORE_HEADER -#define SCTJUDGE_CORE_HEADER - -#include <signal.h> -#include <unistd.h> -#include <sys/time.h> -#include <sys/resource.h> - -#include <pthread.h> -#include <semaphore.h> - -/* mkchild.c */ - -/* 子程序 PID 和對應的 mutex */ -extern pid_t pidchild; -extern pthread_mutex_t pidmutex; - -/* 兩個 thread 的資料 */ -extern pthread_t tkill, tdisplay; -extern char tkill_yes, tdisplay_yes; -extern pthread_mutex_t tkill_mx, tdisplay_mx; - -/* 用來讓另外兩個 thread 卡住的 semaphore */ -extern sem_t mcthr, tlethr, dispthr; - -/* 判斷有無 TLE,此變數由 sctjudge_checktle 設定 */ -extern char judge_tle; -extern pthread_mutex_t judge_tle_mx; - -/* 傳給 sctjudge_makechild 作為參數的 struct */ -struct makechildopt{ - char* executable; - char* chrootdir; - char* inputfile; - char* outputfile; - unsigned exectime; - unsigned memlimit; - unsigned outlimit; - unsigned flags; - uid_t uid; - gid_t gid; -}; - -/* struct makechildopt 裡面 flags 的值 */ -#define SCTMC_REDIR_STDERR 0x00000001 -#define SCTMC_NOCOPY 0x00000002 -#define SCTMC_SETUID 0x00000004 -#define SCTMC_SETGID 0x00000008 -#define SCTMC_DRYRUN 0x00000010 -#define SCTMC_FORCE 0x00000020 -#define SCTMC_VERBOSE 0x00000040 -#define SCTMC_DEBUG 0x00000080 - -/* sctjudge_makechild 的回傳值,main 會接收到 */ -struct makechildrval{ - int mc_exitcode; /* 此與評測結果無關!這是 thread 本身的狀態 */ - int judge_result; /* 就是那個兩三個英文字母的代碼 */ - int judge_exitcode; /* 程式結束回傳值 */ - struct timespec judge_time; /* 執行時間 */ - int judge_signal; /* RE 時的訊號 */ - struct rusage judge_rusage; /* 子程序結束時拿到的 rusage */ -}; - -/* struct makechildrval 裡面 mc_exitcode 的值 */ -#define SCTMCRVAL_SUCCESS 0 -#define SCTMCRVAL_PREPARE 1 /* fork 之前的準備工作出錯 */ -#define SCTMCRVAL_FORK 2 /* fork 失敗 */ -#define SCTMCRVAL_INVALID 3 /* 子程序傳回了不正確的資料(會發生嗎?)*/ -#define SCTMCRVAL_CHILDFAIL 4 /* 子程序在 exec 或之前發生錯誤無法繼續 */ - -/* struct makechildrval 裡面 judge_result 的值 */ -#define SCTRES_OK 0 -#define SCTRES_RE 1 -#define SCTRES_TLE 2 -#define SCTRES_OLE 3 -#define SCTRES_SLE 4 /* 暫停次數太多 */ -#define SCTRES_AB 5 /* 使用者中斷 */ -#define SCTRES_UD 6 /* 未定義的東西,這應該不會出現吧 */ -#define SCTRES_MAX 7 - -void* sctjudge_makechild(void*); - -/* killtle.c */ -extern volatile sig_atomic_t break_flag; -void* sctjudge_checktle(void*); - -/* disptime.c */ -void* sctjudge_displaytime(void*); - -#endif |