├── examples ├── quick-start │ ├── hello-world.in │ └── hello-world.c ├── time-limit-exceed │ ├── tle.out │ ├── build.sh │ └── tle.c └── hello-world │ ├── hello-c │ ├── hello.in │ ├── hello.out │ ├── build.sh │ └── hello.c │ ├── hello-python │ ├── hello.in │ ├── hello.out │ ├── hello.py │ └── build.sh │ ├── hello-cpp │ ├── hello.out │ ├── build.sh │ └── hello.cpp │ └── hello-java │ ├── hello.out │ ├── build.sh │ ├── hello.class │ └── hello.java ├── .gitignore ├── assets ├── 1.png └── 2.png ├── src ├── system │ ├── system.h │ └── system.c ├── judge │ ├── judge.h │ └── judge.c ├── child │ ├── child.h │ └── child.c ├── guard │ ├── guard.h │ └── guard.c ├── time │ ├── time.h │ └── time.c ├── logger │ ├── logger.h │ └── logger.c ├── utils │ └── checker.sh ├── main.c └── common │ ├── common.h │ └── common.c ├── YuJudge-Core.iml ├── CMakeLists.txt ├── README.md └── LICENSE /examples/quick-start/hello-world.in: -------------------------------------------------------------------------------- 1 | 123 -------------------------------------------------------------------------------- /examples/time-limit-exceed/tle.out: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/hello-world/hello-c/hello.in: -------------------------------------------------------------------------------- 1 | 123 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-debug 3 | run -------------------------------------------------------------------------------- /examples/hello-world/hello-python/hello.in: -------------------------------------------------------------------------------- 1 | 100 2 | -------------------------------------------------------------------------------- /examples/hello-world/hello-c/hello.out: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /examples/hello-world/hello-cpp/hello.out: -------------------------------------------------------------------------------- 1 | hello world 2 | -------------------------------------------------------------------------------- /examples/hello-world/hello-java/hello.out: -------------------------------------------------------------------------------- 1 | Hello World 2 | -------------------------------------------------------------------------------- /examples/hello-world/hello-python/hello.out: -------------------------------------------------------------------------------- 1 | 100 2 | hello world 3 | -------------------------------------------------------------------------------- /assets/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuzhanglong/YuJudge-Core/HEAD/assets/1.png -------------------------------------------------------------------------------- /assets/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuzhanglong/YuJudge-Core/HEAD/assets/2.png -------------------------------------------------------------------------------- /examples/hello-world/hello-python/hello.py: -------------------------------------------------------------------------------- 1 | a = input() 2 | print(a) 3 | print("hello world") 4 | -------------------------------------------------------------------------------- /examples/time-limit-exceed/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcc -Wall -O2 -std=gnu11 'tle.c' -o run -lm -------------------------------------------------------------------------------- /examples/hello-world/hello-c/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcc -Wall -O2 -std=gnu11 'hello.c' -o run -lm -------------------------------------------------------------------------------- /examples/hello-world/hello-cpp/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | g++ -Wall -O2 -std=gnu++17 'hello.cpp' -o run 4 | -------------------------------------------------------------------------------- /examples/hello-world/hello-python/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo '#!/bin/sh' > run 4 | echo 'python hello.py' >> run -------------------------------------------------------------------------------- /examples/hello-world/hello-java/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | javac hello.java 4 | echo '#!/bin/sh' > run 5 | echo 'java hello' >> run -------------------------------------------------------------------------------- /examples/hello-world/hello-java/hello.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuzhanglong/YuJudge-Core/HEAD/examples/hello-world/hello-java/hello.class -------------------------------------------------------------------------------- /examples/hello-world/hello-cpp/hello.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main(void) { 6 | cout << "hello world" << endl; 7 | return 0; 8 | } -------------------------------------------------------------------------------- /src/system/system.h: -------------------------------------------------------------------------------- 1 | #ifndef Y_JUDGE_SYSTEM_H 2 | #define Y_JUDGE_SYSTEM_H 3 | 4 | 5 | int killPid(pid_t pid, int killType); 6 | 7 | int isRoot(); 8 | 9 | 10 | #endif //Y_JUDGE_SYSTEM_H 11 | -------------------------------------------------------------------------------- /src/judge/judge.h: -------------------------------------------------------------------------------- 1 | #ifndef Y_JUDGER_JUDGE_H 2 | #define Y_JUDGER_JUDGE_H 3 | 4 | #include "../common/common.h" 5 | 6 | void runJudge(struct execConfig *execConfig, struct judgeResult *judgeResult); 7 | 8 | #endif //Y_JUDGE_JUDGE_H 9 | -------------------------------------------------------------------------------- /examples/hello-world/hello-c/hello.c: -------------------------------------------------------------------------------- 1 | /* 2 | * c 语言的helloworld 3 | * 程序接受用户输入一个整数, 4 | * 程序只有一行输出,为helloworld 5 | */ 6 | 7 | #include 8 | 9 | int main(void) { 10 | int a; 11 | scanf("%d", &a); 12 | printf("hello world\n"); 13 | } -------------------------------------------------------------------------------- /src/child/child.h: -------------------------------------------------------------------------------- 1 | #ifndef Y_JUDGER_CHILD_H 2 | #define Y_JUDGER_CHILD_H 3 | 4 | #include "../common/common.h" 5 | 6 | void setLimitation(struct execConfig *execConfig); 7 | 8 | void runChild(struct execConfig *execConfig); 9 | 10 | #endif //Y_JUDGER_CHILD_H 11 | -------------------------------------------------------------------------------- /examples/hello-world/hello-java/hello.java: -------------------------------------------------------------------------------- 1 | /* 2 | * java 的helloworld 3 | * 程序接受用户输入一个整数, 4 | * 程序只有一行输出,为helloworld 5 | */ 6 | 7 | 8 | public class hello { 9 | public static void main(String[] args) { 10 | System.out.println("Hello World"); 11 | } 12 | } -------------------------------------------------------------------------------- /examples/time-limit-exceed/tle.c: -------------------------------------------------------------------------------- 1 | /* 2 | * 期望的结果: 时间超限(TIME_LIMIT_EXCEED) 3 | * 本程序会一直执行while循环 4 | * 在达到设置的时间限制之后,它会被杀死 5 | * */ 6 | 7 | #include 8 | 9 | int main(void) { 10 | while (1); 11 | printf("you can not reach here....."); 12 | return 0; 13 | } -------------------------------------------------------------------------------- /examples/quick-start/hello-world.c: -------------------------------------------------------------------------------- 1 | /* 2 | * C 语言的 hello world 项目 3 | * 程序接受用户输入一个整数, 4 | * 程序只有一行输出,为 'hello world' 字符串 5 | */ 6 | 7 | #include 8 | 9 | int main(void) { 10 | int a; 11 | scanf("%d", &a); 12 | printf("hello world!\n"); 13 | return 0; 14 | } -------------------------------------------------------------------------------- /src/guard/guard.h: -------------------------------------------------------------------------------- 1 | #ifndef Y_JUDGE_GUARD_H 2 | #define Y_JUDGE_GUARD_H 3 | 4 | // 请参阅Linux系统调用列表 5 | // https://www.ibm.com/developerworks/cn/linux/kernel/syscall/part1/appendix.html 6 | #include 7 | 8 | 9 | void setSeccompGuard(); 10 | 11 | #endif //Y_JUDGE_GUARD_H 12 | -------------------------------------------------------------------------------- /src/time/time.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #ifndef Y_JUDGE_TIME_H 5 | #define Y_JUDGE_TIME_H 6 | 7 | long getTimeMillisecondByTimeval(struct timeval timeval); 8 | 9 | int getGapMillsecond(struct timeval startTime, struct timeval endTime); 10 | 11 | #endif //Y_JUDGE_TIME_H 12 | -------------------------------------------------------------------------------- /src/logger/logger.h: -------------------------------------------------------------------------------- 1 | #ifndef Y_JUDGER_LOGGER_H 2 | #define Y_JUDGER_LOGGER_H 3 | typedef int logType; 4 | 5 | #define FATAL 5 6 | #define ERROR 4 7 | #define WARNING 3 8 | #define INFO 2 9 | #define DEBUG 1 10 | 11 | void makeLog(logType type, char *content, FILE *loggerFile); 12 | 13 | #endif //Y_JUDGER_LOGGER_H 14 | -------------------------------------------------------------------------------- /YuJudge-Core.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/utils/checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 将用户的输出和正确输出进行比较 可供外部项目使用 4 | # 本程序不负责对比,只负责运行用户代码并输出 5 | # 具体的对比或者specialJudge应该交给调用者处理 6 | 7 | # example: ./compare <用户输出> <期望输出> 8 | 9 | 10 | SUBMISSION_OUTPUT_PATH="$1" 11 | RESOLUTION_PATH="$2" 12 | 13 | # 判断文件是否存在 14 | if [ ! -f $SUBMISSION_OUTPUT_PATH ]; then 15 | echo 2 16 | exit 0 17 | elif [ ! -f $RESOLUTION_PATH ]; then 18 | echo 2 19 | exit 0 20 | fi 21 | 22 | # 文件对比 23 | diff -q -b "$SUBMISSION_OUTPUT_PATH" "$RESOLUTION_PATH" 24 | EXIT_CODE=$? 25 | echo $EXIT_CODE -------------------------------------------------------------------------------- /src/system/system.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "system.h" 4 | 5 | /** 6 | * @author yzl 7 | * @param pid: 杀死某个进程id 8 | * @param killType: 类型 9 | * @return int 是否杀死 10 | */ 11 | 12 | int killPid(pid_t pid, int killType) { 13 | // kill pid -9 cannot be caught or ignored 14 | int isKill = kill(pid, killType); 15 | return isKill; 16 | } 17 | 18 | /** 19 | * @author yzl 20 | * @return int 当前用户是否为管理员 21 | */ 22 | 23 | int isRoot() { 24 | uid_t uid = getuid(); 25 | return uid == 0; 26 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(y_judge C) 3 | LINK_LIBRARIES("pthread") 4 | LINK_LIBRARIES("seccomp") 5 | set(CMAKE_C_FLAGS "-O3 -std=gnu99 -pie -fPIC -pthread") 6 | add_executable( 7 | y_judge 8 | src/main.c 9 | src/logger/logger.c 10 | src/logger/logger.h 11 | src/common/common.c 12 | src/common/common.h 13 | src/time/time.c 14 | src/time/time.h 15 | src/system/system.c 16 | src/system/system.h 17 | src/child/child.c 18 | src/child/child.h 19 | src/judge/judge.c 20 | src/judge/judge.h 21 | src/guard/guard.c 22 | src/guard/guard.h) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YuJudge-Core 2 | 3 | ## 关于 4 | 5 | 这是 Online Judge 平台 [YuJudge](https://github.com/yuzhanglong/YuJudge) 的**判题核心**,基于 **C** 编写,支持在 Linux 下运行。 6 | 7 | 本程序基于用户的可执行文件以及标准输入输出目录、资源限制等配置,执行并给出资源消耗情况,并能防止恶意调用。 8 | 9 | 10 | ## 快速开始 11 | 12 | 请参阅[用户手册](https://yuzhanglong.feishu.cn/wiki/wikcnb46K9AS8P42aEFxxTKVmac) 13 | 14 | ## 它是如何工作的? 15 | 16 | 请参考[这篇文章](https://www.zhihu.com/question/20343652/answer/1327243865) 17 | 18 | ## 另请参阅 19 | 20 | 服务端程序:https://github.com/yuzhanglong/YuJudge-JudgeServer 21 | 22 | 判题机程序:https://github.com/yuzhanglong/YuJudge-JudgeHost 23 | 24 | 前端程序:https://github.com/yuzhanglong/YuJudge 25 | 26 | ## License 27 | 28 | MIT[@yuzhanglong](https://github.com/yuzhanglong/) 29 | -------------------------------------------------------------------------------- /src/time/time.c: -------------------------------------------------------------------------------- 1 | #include "time.h" 2 | 3 | /** 4 | * @author yzl 5 | * @param timeval: timeval 结构体 6 | * @return long 总毫秒数 7 | */ 8 | 9 | long getTimeMillisecondByTimeval(struct timeval timeval) { 10 | // startTime.tv_sec : s 11 | // startTime.tv_usec : us 12 | return timeval.tv_sec * 1000 + timeval.tv_usec / 1000; 13 | } 14 | 15 | 16 | /** 17 | * @author yzl 18 | * @param startTime: 开始点的timeval结构体 19 | * @param endTime: 结束点的timeval结构体 20 | * @return int 相差的毫秒数 21 | */ 22 | 23 | int getGapMillsecond(struct timeval startTime, struct timeval endTime) { 24 | long startMillisecond = getTimeMillisecondByTimeval(startTime); 25 | long endMillisecond = getTimeMillisecondByTimeval(endTime); 26 | return (int) (endMillisecond - startMillisecond); 27 | } 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @author:yuzhanglong 3 | * @email:yuzl1123@163.com 4 | * @version: 1.0 5 | * 6 | * 7 | * 注意:请在linux系统下调试运行 8 | * 若你在macos系统下运行(虽然可以跑),会出现你不期望的情况, 9 | * 例如:某些量的单位会不同(eg.costResource.ru_maxrss) 10 | * 或者某些功能无法实现(eg.内存超限检测) 11 | * 12 | */ 13 | #include "common/common.h" 14 | #include "judge/judge.h" 15 | 16 | 17 | int main(int argc, char *argv[]) { 18 | struct execConfig execConfig; 19 | struct judgeResult judgeResult; 20 | initExecConfigAndJudgeResult(&execConfig, &judgeResult); 21 | if (getAndSetOptions(argc, argv, &execConfig)) { 22 | if (validateForExecConfig(&execConfig)) { 23 | runJudge(&execConfig, &judgeResult); 24 | } else { 25 | judgeResult.condition = VALIDATE_ERROR; 26 | } 27 | } 28 | generateResult(&execConfig, &judgeResult); 29 | return 0; 30 | } -------------------------------------------------------------------------------- /src/guard/guard.c: -------------------------------------------------------------------------------- 1 | #include "guard.h" 2 | 3 | int FORBIDDEN_LIST[] = { 4 | // 进程控制 5 | SCMP_SYS(fork), 6 | SCMP_SYS(clone), 7 | SCMP_SYS(vfork) 8 | }; 9 | 10 | 11 | /** 12 | * @author yzl 13 | * @param ctx scmp_filter_ctx 14 | * @return int 15 | * 添加限制规则 16 | */ 17 | int addSeccompRules(scmp_filter_ctx ctx) { 18 | int len = sizeof(FORBIDDEN_LIST) / sizeof(int); 19 | for (int i = 0; i < len; i++) { 20 | if (seccomp_rule_add(ctx, SCMP_ACT_KILL, FORBIDDEN_LIST[i], 0) != 0) { 21 | return 0; 22 | } 23 | } 24 | return 1; 25 | } 26 | 27 | /** 28 | * @author yzl 29 | * @return int 30 | * 执行规则限制 31 | */ 32 | void setSeccompGuard() { 33 | scmp_filter_ctx ctx; 34 | ctx = seccomp_init(SCMP_ACT_ALLOW); 35 | if (!ctx) { 36 | //TODO:限制失效处理 37 | } 38 | addSeccompRules(ctx); 39 | seccomp_load(ctx); 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 yuzhanglong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/logger/logger.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "logger.h" 4 | 5 | /** 6 | * @author yzl 7 | * 输出本日志的创建时间 8 | */ 9 | void outputCurrentTime(FILE *loggerFile) { 10 | 11 | time_t rawTime = time(NULL); 12 | struct tm *currentTime; 13 | currentTime = localtime(&rawTime); 14 | fprintf(loggerFile, "[%d-%d-%d %d:%d:%d]", 15 | currentTime->tm_year + 1900, 16 | currentTime->tm_mon + 1, 17 | currentTime->tm_mday, 18 | currentTime->tm_hour, 19 | currentTime->tm_min, 20 | currentTime->tm_sec 21 | ); 22 | } 23 | 24 | void outputLoggerType(logType type, FILE *loggerFile) { 25 | fprintf(loggerFile, "["); 26 | if (type == DEBUG) { 27 | fprintf(loggerFile, "DEBUG"); 28 | } else if (type == INFO) { 29 | fprintf(loggerFile, "INFO"); 30 | } else if (type == WARNING) { 31 | fprintf(loggerFile, "WARNING"); 32 | } else if (type == ERROR) { 33 | fprintf(loggerFile, "ERROR"); 34 | } else if (type == FATAL) { 35 | fprintf(loggerFile, "FATAL"); 36 | } 37 | fprintf(loggerFile, "]"); 38 | } 39 | 40 | 41 | void makeLog(logType type, char *content, FILE *loggerFile) { 42 | if (loggerFile == NULL) return; 43 | /*输出类型*/ 44 | outputLoggerType(type, loggerFile); 45 | /*获取生成日志的时间*/ 46 | outputCurrentTime(loggerFile); 47 | printf("%s\n", content); 48 | } 49 | 50 | /** 51 | * @author yzl 52 | * 初始化日志记录模块 53 | */ 54 | int initLogger() { 55 | //TODO:处理日志文件名、路径 格式等功能 56 | return 1; 57 | } -------------------------------------------------------------------------------- /src/common/common.h: -------------------------------------------------------------------------------- 1 | #ifndef Y_JUDGER_COMMON_H 2 | #define Y_JUDGER_COMMON_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | enum RUNNING_CONDITION { 9 | RUN_SUCCESS = 1, // 程序通过 10 | RUNTIME_ERROR, // 运行时错误 11 | TIME_LIMIT_EXCEED, // 时间超限 12 | MEMORY_LIMIT_EXCEED, // 内存超限 13 | OUTPUT_LIMIT_EXCEED, // 输出超过限制 14 | SEGMENTATION_FAULT, // 段错误 15 | FLOAT_ERROR, // 浮点错误 16 | UNKNOWN_ERROR, // 未知错误 17 | INPUT_FILE_NOT_FOUND, // 找不到输入文件 18 | CAN_NOT_MAKE_OUTPUT, // 无法寻找输出 19 | SET_LIMIT_ERROR, // 设限失败 20 | NOT_ROOT_USER, // 非管理员用户 21 | FORK_ERROR, //fork失败 22 | CREATE_THREAD_ERROR, //监控线程创建失败 23 | VALIDATE_ERROR // 数据验证失败 24 | }; 25 | 26 | // 对于内存限制的一些实践和解释请参考child.c 27 | enum EXEC_SETTING_DEFAULT { 28 | TIME_LIMIT_DEFAULT = 4000, //cpu实践限制,默认为 4000 ms 29 | MEMORY_LIMIT_DEFAULT = 1024 * 64, // 限制默认内存为64 mb 30 | WALL_MEMORY_DEFAULT = 1024 * 1024 * 3L, // 内存硬限制,请参考child.c 31 | WALL_TIME_DEFAULT = 4000, // 实际时间限制,默认为 4000 ms 32 | PROCESS_LIMIT_DEFAULT = 100, // 进程限制 33 | OUTPUT_LIMIT_DEFAULT = 20000, // 输出限制,默认为 20000 34 | UID_DEFAULT = 3000, 35 | GUARD_DEFAULT = 0 36 | }; 37 | 38 | struct timeoutKillerConfig { 39 | int pid; 40 | int limitTime; 41 | }; 42 | 43 | struct execConfig { 44 | rlim_t cpuTimeLimit; 45 | rlim_t memoryLimit; 46 | rlim_t wallMemoryLimit; 47 | rlim_t processLimit; 48 | rlim_t outputLimit; 49 | rlim_t realTimeLimit; 50 | char *execPath; 51 | char *stdinPath; 52 | char *stdoutPath; 53 | char *stderrPath; 54 | char *loggerPath; 55 | FILE *loggerFile; 56 | uid_t uid; 57 | int guard; 58 | }; 59 | 60 | struct judgeResult { 61 | // 实际消耗时间 62 | rlim_t realTimeCost; 63 | // 消耗内存 64 | rlim_t memoryCost; 65 | // 消耗 CPU 时间 66 | rlim_t cpuTimeCost; 67 | // 执行状态,请参考 RUNNING_CONDITION 枚举类型 68 | int condition; 69 | }; 70 | 71 | void initExecConfigAndJudgeResult(struct execConfig *execConfig, struct judgeResult *judgeResult); 72 | 73 | int validateForExecConfig(struct execConfig *execConfig); 74 | 75 | void showUsage(); 76 | 77 | int getAndSetOptions(int argc, char *argv[], struct execConfig *execConfig); 78 | 79 | void generateResult(struct execConfig *execConfig, struct judgeResult *judgeResult); 80 | 81 | #endif //Y_JUDGER_COMMON_H 82 | -------------------------------------------------------------------------------- /src/child/child.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "child.h" 5 | 6 | #include "../guard/guard.h" 7 | 8 | #define CHILD_EXIT exit 9 | 10 | /** 11 | * @author yzl 12 | * @param execConfig 用户提供的运行的配置 13 | * @return void 14 | * 向(子)进程设限,例如内存、输出限制等限制 15 | */ 16 | 17 | void setLimitation(struct execConfig *execConfig) { 18 | /* 内存超限 19 | * 经过测试发现,内存的限制的精确度较低, 20 | * 我们可以在这里限制一个较大的内存(默认设置了128mb,一般无需更改) 21 | * 我们可以采用如下的方案, 22 | * 为了安全性,硬限制内存确实需要,但是做不到像限制时间那样完美 23 | * 所以我们先为程序设置一个较大的限制值, 24 | * 这个限制值能让用户的代码完整地正常运行 25 | * 同时又不会因为各种奇奇怪怪的情况退出 26 | * 程序运行完成之后我们再来进行比对 27 | */ 28 | struct rlimit maxMemory; 29 | // kb to bytes 30 | maxMemory.rlim_cur = maxMemory.rlim_max = (execConfig->wallMemoryLimit) * 1024; 31 | 32 | if (setrlimit(RLIMIT_AS, &maxMemory) != 0) { 33 | CHILD_EXIT(SET_LIMIT_ERROR); 34 | } 35 | 36 | // 时间超限,传入毫秒单位,我们将其转成setrlimit要求的秒单位,并进位(粗略) 37 | struct rlimit maxTime; 38 | maxTime.rlim_cur = maxTime.rlim_max = (execConfig->cpuTimeLimit / 1000) + 1; 39 | if (setrlimit(RLIMIT_CPU, &maxTime) != 0) { 40 | CHILD_EXIT(SET_LIMIT_ERROR); 41 | } 42 | 43 | // 进程超限 44 | struct rlimit maxProcessAmount; 45 | maxProcessAmount.rlim_cur = maxProcessAmount.rlim_max = execConfig->processLimit; 46 | if (setrlimit(RLIMIT_NPROC, &maxProcessAmount) != 0) { 47 | CHILD_EXIT(SET_LIMIT_ERROR); 48 | } 49 | 50 | 51 | // 输出超限 52 | struct rlimit maxOutput; 53 | maxOutput.rlim_cur = maxOutput.rlim_max = execConfig->outputLimit; 54 | if (setrlimit(RLIMIT_FSIZE, &maxOutput) != 0) { 55 | CHILD_EXIT(SET_LIMIT_ERROR); 56 | } 57 | 58 | // 堆栈 59 | struct rlimit maxStack; 60 | maxStack.rlim_cur = maxStack.rlim_max = RLIM_INFINITY; 61 | if (setrlimit(RLIMIT_STACK, &maxStack) != 0) { 62 | CHILD_EXIT(SET_LIMIT_ERROR); 63 | } 64 | 65 | } 66 | 67 | /** 68 | * @author yzl 69 | * @param execConfig 用户提供的运行的配置 70 | * @return void 71 | * 在fork出孩子进程之后调用之 运行孩子进程(用户提交的代码会在这里被运行) 72 | */ 73 | 74 | void runChild(struct execConfig *execConfig) { 75 | FILE *inputFile = NULL; 76 | FILE *outputFile = NULL; 77 | FILE *errFile = NULL; 78 | 79 | // 重定向输入 80 | if (execConfig->stdinPath[0] != '\0') { 81 | inputFile = fopen(execConfig->stdinPath, "r"); 82 | if (!inputFile) { 83 | CHILD_EXIT(INPUT_FILE_NOT_FOUND); 84 | } 85 | int f = fileno(inputFile); 86 | dup2(f, STDIN_FILENO); 87 | } 88 | 89 | // 重定向标准输出 90 | if (execConfig->stdoutPath[0] != '\0') { 91 | outputFile = fopen(execConfig->stdoutPath, "w"); 92 | if (!outputFile) { 93 | CHILD_EXIT(CAN_NOT_MAKE_OUTPUT); 94 | } 95 | int f2 = fileno(outputFile); 96 | dup2(f2, STDOUT_FILENO); 97 | } 98 | 99 | // 重定向错误输出 100 | if (execConfig->stderrPath[0] != '\0') { 101 | errFile = fopen(execConfig->stderrPath, "w"); 102 | if (!errFile) { 103 | CHILD_EXIT(CAN_NOT_MAKE_OUTPUT); 104 | } 105 | int f3 = fileno(errFile); 106 | dup2(f3, STDERR_FILENO); 107 | } 108 | 109 | setLimitation(execConfig); 110 | 111 | // 设置uid 112 | if (execConfig->uid > 0) { 113 | if (setuid(execConfig->uid) == -1) { 114 | CHILD_EXIT(RUNTIME_ERROR); 115 | } 116 | } 117 | 118 | if (execConfig->guard) { 119 | setSeccompGuard(); 120 | } 121 | 122 | char *envp[] = {"PATH=/bin", 0}; 123 | // 执行用户的提交 124 | execve(execConfig->execPath, NULL, envp); 125 | CHILD_EXIT(EXIT_SUCCESS); 126 | } -------------------------------------------------------------------------------- /src/judge/judge.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "../child/child.h" 8 | #include "../time/time.h" 9 | #include "../system/system.h" 10 | #include "../logger/logger.h" 11 | #include "judge.h" 12 | 13 | /** 14 | * 创建一个监控线程 15 | * 16 | * @author yzl 17 | * @param timeoutKillerConfig 超时状态配置 18 | * @return void * 19 | */ 20 | 21 | void *monitorThread(void *timeoutKillerConfig) { 22 | struct timeoutKillerConfig *timeConf = (struct timeoutKillerConfig *) (timeoutKillerConfig); 23 | // 单独的线程 用来在超时的时候杀死进程 防止超时 24 | pid_t pid = timeConf->pid; 25 | int limitTime = timeConf->limitTime; 26 | sleep((unsigned int) (limitTime)); 27 | killPid(pid, SIGKILL); 28 | return NULL; 29 | } 30 | 31 | /** 32 | * 获取子进程运行状态,应该在wait之后调用 33 | * 34 | * @author yzl 35 | * @param status 子进程状态,一般通过wait4得到 36 | * @param judgeResult 运行结果 37 | * @param execConfig 用户提供的运行的配置 38 | * @return enum RUNNING_CONDITION * 39 | */ 40 | 41 | enum RUNNING_CONDITION getRunningCondition(int status, struct execConfig *execConfig, struct judgeResult *judgeResult) { 42 | // 正常终止 主动抛出的exit都会到这里来 43 | if (WIFEXITED(status)) { 44 | // 对于和期望输出比对的业务逻辑 45 | // 我们交给调动者(判题服务器)来实现 46 | if (WEXITSTATUS(status) == 0) { 47 | // 细粒度判断内存限制,通过杀进程的方式并不准确,详见child.c文件 48 | int isMemoryExceeded = (unsigned long long) (judgeResult->memoryCost) > execConfig->memoryLimit; 49 | if (isMemoryExceeded) { 50 | return MEMORY_LIMIT_EXCEED; 51 | } 52 | 53 | // 细粒度的时间限制(ms) 54 | int isCpuTimeExceeded = execConfig->cpuTimeLimit < judgeResult->cpuTimeCost; 55 | int isRealTimeExceeded = execConfig->realTimeLimit < judgeResult->realTimeCost; 56 | if (isCpuTimeExceeded || isRealTimeExceeded) { 57 | return TIME_LIMIT_EXCEED; 58 | } 59 | return RUN_SUCCESS; 60 | } 61 | return UNKNOWN_ERROR; 62 | } 63 | 64 | // 异常终止 65 | if (WIFSIGNALED(status)) { 66 | if (WTERMSIG(status) == SIGXCPU) { 67 | return TIME_LIMIT_EXCEED; 68 | } 69 | if (WTERMSIG(status) == SIGFPE) { 70 | return FLOAT_ERROR; 71 | } 72 | if (WTERMSIG(status) == SIGSEGV) { 73 | return SEGMENTATION_FAULT; 74 | } 75 | if (WTERMSIG(status) == SIGKILL) { 76 | // 经测试 cpu的时间超限也会出现在此处 77 | if (execConfig->cpuTimeLimit < judgeResult->cpuTimeCost) { 78 | return TIME_LIMIT_EXCEED; 79 | } 80 | return RUNTIME_ERROR; 81 | } 82 | if (WTERMSIG(status) == SIGXFSZ) { 83 | return OUTPUT_LIMIT_EXCEED; 84 | } 85 | return RUNTIME_ERROR; 86 | } 87 | return UNKNOWN_ERROR; 88 | } 89 | 90 | 91 | /** 92 | * 运行程序, 写入运行结果 93 | * @author yzl 94 | * @param judgeResult 最终运行结果 95 | * @param execConfig 用户提供的运行的配置 96 | * @return void 97 | */ 98 | 99 | void runJudge(struct execConfig *execConfig, struct judgeResult *judgeResult) { 100 | struct timeval startTime, endTime; 101 | gettimeofday(&startTime, NULL); 102 | if (!isRoot()) { 103 | makeLog(WARNING, "非root用户", execConfig->loggerFile); 104 | judgeResult->condition = NOT_ROOT_USER; 105 | return; 106 | } 107 | 108 | pid_t childPid = fork(); 109 | pthread_t pthread = 0; 110 | 111 | if (childPid < 0) { 112 | //如果出现错误,fork返回一个负值 113 | makeLog(ERROR, "fork error!", execConfig->loggerFile); 114 | judgeResult->condition = FORK_ERROR; 115 | return; 116 | } 117 | 118 | if (childPid == 0) { 119 | runChild(execConfig); 120 | } 121 | 122 | if (childPid > 0) { 123 | // 父亲进程 124 | makeLog(DEBUG, "父进程已创建", execConfig->loggerFile); 125 | struct timeoutKillerConfig killerConfig; 126 | killerConfig.limitTime = execConfig->realTimeLimit / 1000; 127 | killerConfig.pid = childPid; 128 | 129 | // 若线程创建成功,则返回0。若线程创建失败,则返回出错编号 130 | int t = pthread_create(&pthread, NULL, monitorThread, (void *) (&killerConfig)); 131 | if (t != 0) { 132 | judgeResult->condition = CREATE_THREAD_ERROR; 133 | return; 134 | } 135 | int status = 0; 136 | struct rusage costResource; 137 | 138 | // 等待孩子进程执行 孩子被杀死或者正常执行都会走到这里 139 | wait4(childPid, &status, WSTOPPED, &costResource); 140 | 141 | // 销毁监控进程 142 | pthread_cancel(pthread); 143 | makeLog(DEBUG, "监控线程被销毁", execConfig->loggerFile); 144 | gettimeofday(&endTime, NULL); 145 | 146 | // cpu时间 注意和真实时间区分 147 | judgeResult->cpuTimeCost = (int) getTimeMillisecondByTimeval(costResource.ru_utime); 148 | judgeResult->realTimeCost = getGapMillsecond(startTime, endTime); 149 | //WARNING:该值在mac和linux系统上是有区别的! 150 | judgeResult->memoryCost = costResource.ru_maxrss; 151 | judgeResult->condition = getRunningCondition(status, execConfig, judgeResult); 152 | } 153 | } -------------------------------------------------------------------------------- /src/common/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "common.h" 5 | 6 | #define VALIDATE_CONFIG_ERROR 0 7 | #define VALIDATE_SUCCESS 1 8 | 9 | /** 10 | * @author yzl 11 | * 在命令行展示程序的用法 12 | */ 13 | void showUsage() { 14 | printf("\n[限制相关]\n"); 15 | printf("\ 16 | -t, [可选] 限制实际时间,单位 ms,请注意和 CPU 时间区分,默认值为 4000\n\ 17 | -c, [可选] 限制 CPU 时间,单位 ms,默认值为 4000\n\ 18 | -m, [可选] 限制运行内存,单位 KB,默认值为 1024 * 1024\n\ 19 | -u, [可选] 执行目标可执行文件对应的用户 ID,默认值为 3000\n\ 20 | -g, [可选] 是否开启 seccomp 限制沙箱,默认为 0 (不开启),设置为 1 则开启\n\ 21 | -f, [可选] 限制代码最大输出,单位 B,默认值为 20000\n"); 22 | 23 | printf("[输入/输出相关]\n"); 24 | 25 | printf("\ 26 | -r, [可选] 目标可执行文件\n\ 27 | -l, [可选] 日志文件\n\ 28 | -o, [可选] 标准输出文件\n\ 29 | -e, [可选] 标准错误文件\n\ 30 | -i, [可选] 标准输入文件\n"); 31 | 32 | printf("[其他]\n"); 33 | printf("\ 34 | -h, 查看帮助\n\n"); 35 | } 36 | 37 | /** 38 | * @author yzl 39 | * @param execConfig 运行配置 40 | * @return void 41 | * 初始化用户配置 42 | */ 43 | 44 | void initExecConfigAndJudgeResult(struct execConfig *execConfig, struct judgeResult *judgeResult) { 45 | execConfig->memoryLimit = MEMORY_LIMIT_DEFAULT; 46 | execConfig->cpuTimeLimit = TIME_LIMIT_DEFAULT; 47 | execConfig->realTimeLimit = WALL_TIME_DEFAULT; 48 | execConfig->processLimit = PROCESS_LIMIT_DEFAULT; 49 | execConfig->outputLimit = OUTPUT_LIMIT_DEFAULT; 50 | execConfig->wallMemoryLimit = WALL_MEMORY_DEFAULT; 51 | execConfig->uid = UID_DEFAULT; 52 | execConfig->guard = GUARD_DEFAULT; 53 | execConfig->execPath = "\0"; 54 | execConfig->stderrPath = "\0"; 55 | execConfig->stdoutPath = "\0"; 56 | execConfig->stdinPath = "\0"; 57 | execConfig->loggerPath = "\0"; 58 | execConfig->execPath = "\0"; 59 | execConfig->loggerFile = NULL; 60 | judgeResult->condition = 1; 61 | judgeResult->memoryCost = 0; 62 | judgeResult->realTimeCost = 0; 63 | judgeResult->cpuTimeCost = 0; 64 | } 65 | 66 | /** 67 | * @author yzl 68 | * @param execConfig 用户提供的运行的配置 69 | * @return void 70 | * 验证用户配置的合法性 71 | */ 72 | 73 | int validateForExecConfig(struct execConfig *execConfig) { 74 | if (execConfig->cpuTimeLimit < 0 75 | || execConfig->memoryLimit < 1024 76 | || execConfig->realTimeLimit < 0 77 | || execConfig->processLimit < 0 78 | || execConfig->outputLimit < 0 79 | || execConfig->execPath[0] == '\0') { 80 | return VALIDATE_CONFIG_ERROR; 81 | } 82 | return VALIDATE_SUCCESS; 83 | } 84 | 85 | /** 86 | * 获取用户配置,并设置用户配置 87 | * 88 | * @author yzl 89 | * @param argc 用户传入参数的个数 90 | * @param argv 用户传入的参数 91 | * @param execConfig 运行配置 92 | * @return int 是否设置成功,如果成功,程序将继续执行 93 | */ 94 | 95 | int getAndSetOptions(int argc, char *argv[], struct execConfig *execConfig) { 96 | int opt; 97 | if (argc == 1) { 98 | showUsage(); 99 | return 0; 100 | } 101 | while ((opt = getopt(argc, argv, "t:c:m:f:o:e:i:r:l:h:u:g:p:")) != -1) { 102 | switch (opt) { 103 | case 't': 104 | execConfig->realTimeLimit = atoi(optarg); 105 | break; 106 | case 'c': 107 | execConfig->cpuTimeLimit = atoi(optarg); 108 | break; 109 | case 'm': 110 | execConfig->memoryLimit = atoi(optarg); 111 | break; 112 | case 'f': 113 | execConfig->outputLimit = atoi(optarg); 114 | break; 115 | case 'o': 116 | execConfig->stdoutPath = optarg; 117 | break; 118 | case 'e': 119 | execConfig->stderrPath = optarg; 120 | break; 121 | case 'i': 122 | execConfig->stdinPath = optarg; 123 | break; 124 | case 'r': 125 | execConfig->execPath = optarg; 126 | break; 127 | case 'l': 128 | execConfig->loggerPath = optarg; 129 | execConfig->loggerFile = fopen(execConfig->loggerPath, "w"); 130 | break; 131 | case 'u': 132 | execConfig->uid = atoi(optarg); 133 | break; 134 | case 'g': 135 | execConfig->guard = atoi(optarg); 136 | break; 137 | case 'p': 138 | execConfig->processLimit = atoi(optarg); 139 | break; 140 | case 'h': 141 | showUsage(); 142 | return 0; 143 | default: 144 | printf("Unknown option: %c\n", (char) optopt); 145 | return 0; 146 | } 147 | } 148 | return 1; 149 | } 150 | 151 | /** 152 | * 运行结束,输出结果 153 | * 154 | * @author yzl 155 | * @param execConfig 运行参数 156 | * @param judgeResult 运行结果 157 | */ 158 | 159 | void generateResult(struct execConfig *execConfig, struct judgeResult *judgeResult) { 160 | // 此处的stdout将被调用者处理 应该以json字符串形式表示 161 | printf("{\n" 162 | " \"realTimeCost\": %llu,\n" 163 | " \"cpuTimeCost\": %llu,\n" 164 | " \"memoryCost\": %llu,\n" 165 | " \"condition\": %d,\n" 166 | " \"stdinPath\": \"%s\",\n" 167 | " \"stdoutPath\": \"%s\",\n" 168 | " \"stderrPath\": \"%s\",\n" 169 | " \"loggerPath\": \"%s\"\n" 170 | "}\n", 171 | judgeResult->realTimeCost, 172 | judgeResult->cpuTimeCost, 173 | judgeResult->memoryCost, 174 | judgeResult->condition, 175 | execConfig->stdinPath, 176 | execConfig->stdoutPath, 177 | execConfig->stderrPath, 178 | execConfig->loggerPath 179 | ); 180 | } --------------------------------------------------------------------------------