Skip to content

Conversation

@aberter0x3f
Copy link

TL;DR:升级 Ubuntu docker image 至 24.04,编译器至软件源中最高版本.并下放部分 UOJ 官网版特性至社区版.

本次更新不破坏原有数据库和存储格式等,理论来讲升级后不会影响原有 OJ 正常工作.


摘要

本次更新是对 UOJ 评测系统的一次大规模重构和现代化升级.核心变更在于引入了全新的 Judger V2,它基于 seccompptrace 实现了更安全、更强大的沙箱机制.同时,我们将 webjudger 的基础镜像从 Ubuntu 20.04 全面升级至 Ubuntu 24.04,并更新了相关的工具链(如 GCC 14, OpenJDK 21),扩展了对 C++ 和 Java 多版本标准的支持.

主要变更

1. 核心评测机重构 (Judger V2)

  • 全新沙箱机制:重写了 run_program,采用 seccomp + ptrace 实现沙箱,相比旧版仅依赖 ptrace 的方式,提供了更强的安全性和更精细的系统调用控制.
  • 评测逻辑框架化:引入了全新的评测框架 uoj_judger_v2.h,将原先硬编码在 judger.cpp 中的评测流程(如处理子任务、打包测试、Hack 等)抽象为更通用、更易于扩展的 C++ 类和函数.这使得为特殊题型编写自定义 judger 变得更加简单和清晰.
  • 编译过程解耦:新增了独立的 run/compile 程序,负责处理所有语言的编译任务.Web 后端现在通过调用此程序来编译题目数据(如 std, checker),不再直接调用 g++ 等编译器,增强了模块化和可维护性.同时此特性同步官网版类似 language_suffix 的效果.可使用 chk17.cpp、val20.cpp 等名称调用指定版本的编译参数进行编译.
  • 浮点数分数支持:引入了 score_t 类型,评测核心现在原生支持浮点数分数.但为了保证数据库结构兼容性,存储到数据库时整题的分数四舍五入,但每个测试点分数保留实数.
  • 依赖更新:将核心评测库 testlib.h0.9.5 大幅升级至 0.9.44,带来了大量新功能和稳定性修复.

2. 环境现代化与 Docker 改进

  • 基础镜像升级webjudger 的 Docker 镜像均从 Ubuntu 20.04 升级至 24.04.
  • 工具链更新
    • Judger 环境中的编译器升级至 GCC 14,Java 升级至 OpenJDK 21.
    • 由于 Ubuntu 24.04 不再提供 Python 2,Judger 镜像现在从源码编译安装 Python 2.7 以保持兼容性.
    • Web 镜像通过 PPA 将 PHP 版本固定为 7.4,并修复了 v8js 扩展在 GCC 14 下的编译问题.
  • Docker Compose 优化
    • uoj-dbuoj-web 服务添加了 healthcheck,并使用 depends_on.condition 确保服务按健康状态顺序启动,解决了启动时可能出现的竞态问题.
    • web 容器的启动命令改为 apache2ctl -D FOREGROUND,这是在 Docker 中运行 Apache 的标准实践.

3. 语言支持扩展

  • C++:新增了对 C++98, C++03, C++14, C++17, C++20, C++23, C++26 等多个标准的支持.
  • Java:新增了对 Java 17 和 Java 21 的支持.
  • 前端和后端的语言选择列表已同步更新.
  • C、C++ 语言选项被移出语言列表,现在不可以在评测时被选择.原有语言为 C/C++ 的提交和 .cpp 后缀的 chk、val 等程序会被视为 C17/C++17 评测,仍可正常 rejudge.且数据库中原有的 "C++" 值不会变动.

4. 代码质量与修复

  • 代码风格统一:添加了 .editorconfig.clang-format 文件,以规范化代码风格.
  • 功能增强yesno checker 现在可以比较一连串的 "YES" 或 "NO",而不仅仅是单个.
  • Bug 修复
    • 修复了 judge_clientisAlive() 的 Python 3 语法兼容性问题.
    • 修复了 uojRandAvaiable... 系列函数名的拼写错误,统一为 uojRandAvailable...
    • 更新了 FAQ 页面中关于编译环境的描述.

@renbaoshuo renbaoshuo self-assigned this Oct 14, 2025
@renbaoshuo
Copy link
Member

还没看代码,但是原有的C++应该被映射为之前版本编译器的默认版本吧,是C++14还是C++11还是C++98来着,记不太清了。

@aberter0x3f
Copy link
Author

aberter0x3f commented Oct 14, 2025

还没看代码,但是原有的C++应该被映射为之前版本编译器的默认版本吧,是C++14还是C++11还是C++98来着,记不太清了。

原来的 G++ 是 9.X 版本的,默认不用 -std 参数时是一个「能用部分 C++17 特性的 C++14」,为了最大化兼容干脆设置成 C++17.另外 C++17 这个标准在语言特性上算是个比较 minor 的更新,「没删多少东西」,不像 C++20 这样子删了许多「不安全的 API,比如 cin>>(char*)」,大概率用 C++17 没有问题.

@renbaoshuo
Copy link
Member

另:关于 judger 相关的代码可以参考一下 renbaoshuo/S2OJ#2 ,我当时记得是没有专门做适配也可以使用的,这两个 PR 里面的 diff 应该很相似,实测在 renbaoshuo/S2OJ#2 中的改动对于社区版上数千道存量题目是没有什么影响的。

@renbaoshuo
Copy link
Member

还有就是

Judger 镜像现在从源码编译安装 Python 2.7 以保持兼容性

这样是否会导致构建的时候更容易出现问题?能不能直接下载现成的 deb 包,或者干脆考虑放弃对 Python 2 的支持(目前市占率很低了吧;cc @billchenchina

@renbaoshuo renbaoshuo requested review from Copilot and renbaoshuo and removed request for renbaoshuo October 14, 2025 15:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This is a major upgrade and refactoring of the UOJ system, transitioning from Ubuntu 20.04 to 24.04 and introducing a new Judger V2 with enhanced security and language support.

  • Core judger system rewritten with new sandbox mechanism based on seccomp + ptrace
  • Docker environment upgraded from Ubuntu 20.04 to 24.04 with updated toolchain (GCC 14, OpenJDK 21)
  • Extended language support including multiple C/C++ and Java versions

Reviewed Changes

Copilot reviewed 37 out of 39 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
web/js/uoj.js Added support for new C/C++ and Java language versions in CodeMirror
web/app/libs/uoj-rand-lib.php Fixed spelling error in function names from "Avaiable" to "Available"
web/app/libs/uoj-judger-lib.php Updated supported languages list with new C/C++ and Java versions
web/app/libs/uoj-html-lib.php Extended syntax highlighting support for new language versions
web/app/libs/uoj-data-lib.php Enhanced compilation system with new compiler interface
web/app/controllers/*.php Updated function calls to use corrected spelling
web/Dockerfile Upgraded to Ubuntu 24.04 with updated dependencies
judger/uoj_judger/run/*.cpp Complete rewrite of judger system with new sandbox implementation
judger/uoj_judger/include/*.h New header files for secure execution and run utilities
judger/uoj_judger/builtin/* Updated judger and checker implementations
docker-compose.yml Added health checks and updated service dependencies
Comments suppressed due to low confidence (1)

judger/uoj_judger/run/run_program_sandbox.h:1

  • Missing break; statements after cases 6, 7, and 8, causing fall-through behavior which is likely unintended in this state machine.
#pragma once

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@aberter0x3f
Copy link
Author

还有就是

Judger 镜像现在从源码编译安装 Python 2.7 以保持兼容性

这样是否会导致构建的时候更容易出现问题?能不能直接下载现成的 deb 包,或者干脆考虑放弃对 Python 2 的支持(目前市占率很低了吧;cc @billchenchina

首先是真没找到合适的 ppa,否则不会采取手动编译这个做法.

phase1 的改动设计准则是完全不动原有的数据库结构和存储格式,并且保证原有提交必须几乎都能重测,于是就保留了 Python2.

移除 Python2 计划要 phase2 实施.

Copy link
Member

@renbaoshuo renbaoshuo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

尚未 review 的部分:

  • web/app/libs/uoj-data-lib.php
  • judger/uoj_judger 下的文件

除此之外,可能考虑在合并 #152 后再合并这个 PR。移除 PHPv8 相关组件以后会减少项目整体的复杂度(特别是构建 PHPv8 时出现的高失败率和长耗时)。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感觉引入了自己的 clang-format 以后不太好和上游官网版 diff 差异部分?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

引入的原因是因为原本的文件风格太混乱了,甚至出现 tab space 混用,大括号换不换行混用等.

目前的 .clang-format 是用 AI「拟合之前的代码风格,选择与原来最接近的样式」而形成的.

你可以把上游也 clang-format 一下然后来跑 diff.

<?php
global $uojSupportedLanguages, $uojMainJudgerWorkPath;
$uojSupportedLanguages = array('C', 'C++', 'C++11', 'Java8', 'Java11', 'Pascal', 'Python2', 'Python3');
$uojSupportedLanguages = array('C89', 'C99', 'C11', 'C17', 'C23', 'C++98', 'C++03', 'C++11', 'C++14', 'C++17', 'C++20', 'C++23', 'C++26', 'Java8', 'Java11', 'Java17', 'Java21', 'Pascal', 'Python2', 'Python3');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

原先的存量 C++ 提交是否需要搞个 upgrade_map 来做映射呢?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

或者参考官网版,做一个显示的 mapping,使得 C++03 在存储时仍为 C++ 而非带有具体版本号的语言代码。

https://github.com/vfleaking/uoj/blob/517134629ba066c45c7d8637cfc98b75acb560da/web/app/models/UOJLang.php#L4-L19

Copy link
Author

@aberter0x3f aberter0x3f Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

完全没有必要,首先因为原来的「C++」不对应任何一个现在的 C++ 版本(你没法认为一个「用旧版编译器编译的,支持了部分 C++ 17 特性的 gnu++14」是哪个 C++ 版本),如果强制把原来的 C++ 显示为 C++ XX 或者把数据库里的 C++ 全部 overwrite 成 C++ XX 本身就不对.比方说你可能有重测那些「旧提交」的需求什么的,就可以选择这个兼容的 C++ 语言.

其次是没有后缀的 C++ 在重构后不应该在被作为新提交的语言添加进数据库里了:C++ 被隐藏掉了,你无法通过正常手段创建一个语言为 C++ 的提交了.

同时这个语言的存在本身就是个错误,虽然在评测逻辑里 C++ = C++17,但它本质上和固定标准的 C++17 是「两种语言」,不应该出现「之后的 C++ 17 在数据库里记为 C++」这种迷惑设定.

@renbaoshuo
Copy link
Member

留一下这个评论发布时,这个 PR 中的 Judger 和 S2OJ Judger 的 diff:

diff with /judger/uoj_judger in renbaoshuo/S2OJ@86b9b47
diff -Nrau a/builtin/judger/judger.cpp b/builtin/judger/judger.cpp
--- a/builtin/judger/judger.cpp	2025-10-16 21:08:05.214404203 +0800
+++ b/builtin/judger/judger.cpp	2025-10-12 23:03:04.180902431 +0800
@@ -1,3 +1,5 @@
+#include <set>
+
 #include "uoj_judger.h"
 
 PointInfo submit_answer_test_point_with_auto_generation(int i,
diff -Nrau a/include/uoj_judger.h b/include/uoj_judger.h
--- a/include/uoj_judger.h	2025-10-16 21:08:10.845443690 +0800
+++ b/include/uoj_judger.h	2025-10-16 20:48:32.270853381 +0800
@@ -1,3 +1,5 @@
+#pragma once
+
 #include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
@@ -14,7 +16,6 @@
 #include <fstream>
 #include <iostream>
 #include <map>
-#include <set>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -22,7 +23,6 @@
 #include "uoj_run.h"
 #include "uoj_secure.h"
 
-namespace fs = std::filesystem;
 using namespace std;
 
 /*========================== string ====================== */
@@ -261,7 +261,7 @@
 const RunLimit RL_CHECKER_DEFAULT = RunLimit(5, 256, 64);
 const RunLimit RL_INTERACTOR_DEFAULT = RunLimit(1, 256, 64);
 const RunLimit RL_VALIDATOR_DEFAULT = RunLimit(5, 256, 64);
-const RunLimit RL_COMPILER_DEFAULT = RunLimit(15, 512, 64);
+const RunLimit RL_COMPILER_DEFAULT = RunLimit(15, 2048, 64);
 
 struct InfoBlock {
 	string title;
@@ -791,32 +791,16 @@
 score_t conf_score(const string &key, int num, const score_t &val) {
 	return score_t(conf_double(key, num, double(val))).rounded_score();
 }
-string conf_file_name_with_num(string s, int num) {
-	ostringstream name;
-	if (num < 0) {
-		name << "ex_";
-	}
-	name << conf_str(s + "_pre", s) << abs(num) << "." << conf_str(s + "_suf", "txt");
-	return name.str();
-}
-string conf_input_file_name(int num) {
-	// return conf_file_name_with_num("input", num):
-	ostringstream name;
-	if (num < 0) {
-		name << "ex_";
-	}
-	name << conf_str("input_pre", "") << abs(num) << "." << conf_str("input_suf", "in");
-	return name.str();
-}
-string conf_output_file_name(int num) {
-	// return conf_file_name_with_num("output", num):
+string conf_file_name_with_num(const string &s, int num) {
 	ostringstream name;
 	if (num < 0) {
 		name << "ex_";
 	}
-	name << conf_str("output_pre", "") << abs(num) << "." << conf_str("output_suf", "out");
+	name << conf_str(s + "_pre", s) << std::abs(num) << "." << conf_str(s + "_suf", "txt");
 	return name.str();
 }
+string conf_input_file_name(int num) { return conf_file_name_with_num("input", num); }
+string conf_output_file_name(int num) { return conf_file_name_with_num("output", num); }
 runp::limits_t conf_run_limit(string pre, const int &num, const runp::limits_t &val) {
 	if (!pre.empty()) {
 		pre += "_";
@@ -1073,7 +1057,10 @@
 		for (vector<string>::const_iterator it = argv.begin(); it != argv.end(); it++) {
 			sout << " " << escapeshellarg(*it);
 		}
-		return sout.str();
+
+		string command = sout.str();
+
+		return command;
 	}
 };
 
@@ -1097,7 +1084,9 @@
 		sout << " " << escapeshellarg(*it);
 	}
 
-	if (execute(sout.str()) != 0) {
+	string command = sout.str();
+
+	if (execute(command) != 0) {
 		return RunResult::failed_result();
 	}
 	return RunResult::from_file(run_program_result_file_name);
@@ -1751,7 +1740,7 @@
 	PointInfo::show_out = conf_str("show_out", "on") == "on";
 	PointInfo::show_res = conf_str("show_res", "on") == "on";
 
-	string score_type = conf_str("score_type", "real-2");
+	string score_type = conf_str("score_type", "int");
 	if (score_type == "int") {
 		score_t::mode = SM_INT;
 	} else {
diff -Nrau a/include/uoj_judger_v2.h b/include/uoj_judger_v2.h
--- a/include/uoj_judger_v2.h	2025-10-16 21:08:12.377454444 +0800
+++ b/include/uoj_judger_v2.h	2025-10-16 20:48:32.270853381 +0800
@@ -1,3 +1,5 @@
+#pragma once
+
 #include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
@@ -12,10 +14,9 @@
 #include <ctime>
 #include <filesystem>
 #include <fstream>
+#include <functional>
 #include <iostream>
 #include <map>
-#include <memory>
-#include <set>
 #include <sstream>
 #include <string>
 #include <vector>
@@ -291,7 +292,7 @@
 const runp::limits_t RL_INTERACTOR_DEFAULT(1, 256, 64);
 const runp::limits_t RL_VALIDATOR_DEFAULT(5, 256, 64);
 const runp::limits_t RL_TRANSFORMER_DEFAULT(30, 512, 256);
-const runp::limits_t RL_COMPILER_DEFAULT(15, 512, 64);
+const runp::limits_t RL_COMPILER_DEFAULT(15, 2048, 64);
 
 struct InfoBlock {
 	string title;
@@ -586,22 +587,8 @@
 	name << conf_str(s + "_pre", s) << abs(num) << "." << conf_str(s + "_suf", "txt");
 	return name.str();
 }
-string conf_input_file_name(int num) {
-	ostringstream name;
-	if (num < 0) {
-		name << "ex_";
-	}
-	name << conf_str("input_pre", "") << abs(num) << "." << conf_str("input_suf", "in");
-	return name.str();
-}
-string conf_output_file_name(int num) {
-	ostringstream name;
-	if (num < 0) {
-		name << "ex_";
-	}
-	name << conf_str("output_pre", "") << abs(num) << "." << conf_str("output_suf", "out");
-	return name.str();
-}
+string conf_input_file_name(int num) { return conf_file_name_with_num("input", num); }
+string conf_output_file_name(int num) { return conf_file_name_with_num("output", num); }
 runp::limits_t conf_run_limit(string pre, const int &num, const runp::limits_t &val) {
 	if (!pre.empty()) {
 		pre += "_";
@@ -950,7 +937,7 @@
 	string data_path_std = data_path / "interactor";
 	string work_path_std = work_path / "interactor";
 	executef("cp %s %s", data_path_std.c_str(), work_path_std.c_str());
-	conf_add("interactor_language", "C++");
+	conf_add("interactor_language", "C++17");
 	prepared = true;
 }
 
diff -Nrau a/include/uoj_run.h b/include/uoj_run.h
--- a/include/uoj_run.h	2025-10-16 21:08:13.908465194 +0800
+++ b/include/uoj_run.h	2025-10-13 23:01:08.974118044 +0800
@@ -1,24 +1,27 @@
+#pragma once
+
 #include <sys/time.h>
 
 #include <cmath>
 #include <cstdarg>
-#include <exception>
 #include <filesystem>
 #include <fstream>
+#include <iostream>
 #include <map>
 #include <sstream>
 #include <stdexcept>
 #include <string>
 #include <vector>
 
-#define UOJ_GCC "/usr/bin/gcc-11"
-#define UOJ_GPLUSPLUS "/usr/bin/g++-11"
-#define UOJ_PYTHON2_7 "/usr/bin/python2.7"
-#define UOJ_PYTHON3 "/usr/bin/python3.10"
+#define UOJ_GCC "/usr/bin/gcc"
+#define UOJ_GPLUSPLUS "/usr/bin/g++"
+#define UOJ_PYTHON2 "/usr/local/bin/python2.7"
+#define UOJ_PYTHON3 "/usr/bin/python3.12"
+#define UOJ_PYTHON3_VERSION "3.12"
 #define UOJ_FPC "/usr/bin/fpc"
-#define UOJ_OPEN_JDK8 "/usr/lib/jvm/java-8-openjdk-amd64"
-#define UOJ_OPEN_JDK11 "/usr/lib/jvm/java-11-openjdk-amd64"
-#define UOJ_OPEN_JDK17 "/usr/lib/jvm/java-17-openjdk-amd64"
+#define UOJ_FPC_VERSION "3.2.2"
+#define UOJ_JDK "/usr/lib/jvm/java-21-openjdk-amd64"
+#define UOJ_JAVA_VERSION "21"
 
 std::string escapeshellarg(int arg) { return std::to_string(arg); }
 std::string escapeshellarg(double arg) {
@@ -74,7 +77,7 @@
 }
 
 int executef(const char *fmt, ...) {
-	const int L = 1 << 10;
+	constexpr int L = 1 << 10;
 	char cmd[L];
 	va_list ap;
 	va_start(ap, fmt);
@@ -125,9 +128,8 @@
 }
 
 std::map<std::string, std::string> lang_upgrade_map = {
-    {"Java7", "Java8"},
-    {"Java14", "Java17"},
-    {"Python2", "Python2.7"},
+    {"C", "C17"},
+    {"C++", "C++17"},
 };
 
 std::string upgraded_lang(const std::string &lang) {
@@ -187,16 +189,12 @@
 
 inline std::string get_type_from_lang(std::string lang) {
 	lang = upgraded_lang(lang);
-	if (lang == "Python2.7") {
-		return "python2.7";
+	if (lang == "Python2") {
+		return "python2";
 	} else if (lang == "Python3") {
 		return "python3";
-	} else if (lang == "Java8") {
-		return "java8";
-	} else if (lang == "Java11") {
-		return "java11";
-	} else if (lang == "Java17") {
-		return "java17";
+	} else if (lang.size() >= 4 && lang.substr(0, 4) == "Java") {
+		return "java";
 	} else {
 		return "default";
 	}
@@ -239,7 +237,7 @@
 		}
 		res.type = (RS_TYPE)type;
 
-		int L = 1 << 15;
+		constexpr int L = 1 << 15;
 		char buf[L];
 		while (!feof(fres)) {
 			int c = fread(buf, 1, L, fres);
@@ -307,7 +305,7 @@
 	std::string program_name;
 	std::vector<std::string> rest_args;
 
-	// full args (possbily with interpreter)
+	// full args (possibly with interpreter)
 	std::vector<std::string> full_args;
 
 	bool unsafe = false;
@@ -374,23 +372,15 @@
 		full_args.push_back(program_name);
 		full_args.insert(full_args.end(), rest_args.begin(), rest_args.end());
 
-		if (type == "java8" || type == "java11" || type == "java17") {
+		if (type == "java") {
 			full_args[0] = get_class_name_from_file(fs::path(full_args[0]) / ".main_class_name");
 
-			std::string jdk;
-			if (type == "java8") {
-				jdk = UOJ_OPEN_JDK8;
-			} else if (type == "java11") {
-				jdk = UOJ_OPEN_JDK11;
-			} else {  // if (type == "java17") {
-				jdk = UOJ_OPEN_JDK17;
-			}
 			full_args.insert(
 			    full_args.begin(),
-			    {fs::canonical(fs::path(jdk) / "bin" / "java"), "-Xmx2048m", "-Xss1024m",
+			    {fs::canonical(fs::path(UOJ_JDK) / "bin" / "java"), "-Xmx2048m", "-Xss1024m",
 			     "-XX:ActiveProcessorCount=1", "-classpath", program_name});
-		} else if (type == "python2.7") {
-			full_args.insert(full_args.begin(), {UOJ_PYTHON2_7, "-E", "-s", "-B"});
+		} else if (type == "python2") {
+			full_args.insert(full_args.begin(), {UOJ_PYTHON2, "-E", "-s", "-B"});
 		} else if (type == "python3") {
 			full_args.insert(full_args.begin(), {UOJ_PYTHON3, "-I", "-B"});
 		}
diff -Nrau a/include/uoj_secure.h b/include/uoj_secure.h
--- a/include/uoj_secure.h	2025-10-16 21:08:15.561476807 +0800
+++ b/include/uoj_secure.h	2025-10-12 22:22:40.093351710 +0800
@@ -1,3 +1,5 @@
+#pragma once
+
 #include <cstring>
 #include <iostream>
 #include <sstream>
diff -Nrau a/run/compile.cpp b/run/compile.cpp
--- a/run/compile.cpp	2025-10-16 21:08:19.797506587 +0800
+++ b/run/compile.cpp	2025-10-14 14:17:47.099371701 +0800
@@ -3,7 +3,6 @@
 #include <cstdio>
 #include <fstream>
 #include <iostream>
-#include <map>
 #include <sstream>
 #include <stdexcept>
 #include <string>
@@ -33,11 +32,33 @@
 };
 
 const std::vector<std::pair<const char *, const char *>> suffix_search_list = {
-    {".code", ""},         {"20.cpp", "C++20"},   {"17.cpp", "C++17"},     {"14.cpp", "C++"},
-    {"11.cpp", "C++11"},   {"03.cpp", "C++03"},   {"98.cpp", "C++98"},     {".cpp", "C++"},
-    {".c", "C"},           {".pas", "Pascal"},    {"2.7.py", "Python2.7"}, {"2.py", "Python2.7"},
-    {".py", "Python3"},    {"7.java", "Java7"},   {"8.java", "Java8"},     {"11.java", "Java11"},
-    {"14.java", "Java14"}, {"17.java", "Java17"},
+    {".code", ""},
+    // C++
+    {"98.cpp", "C++98"},
+    {"03.cpp", "C++03"},
+    {"11.cpp", "C++11"},
+    {"14.cpp", "C++14"},
+    {"17.cpp", "C++17"},
+    {"20.cpp", "C++20"},
+    {"23.cpp", "C++23"},
+    {"26.cpp", "C++26"},
+    {".cpp", "C++17"},
+    // C
+    {"89.c", "C89"},
+    {"99.c", "C99"},
+    {"11.c", "C11"},
+    {"17.c", "C17"},
+    {"23.c", "C23"},
+    {".c", "C17"},
+    // Java
+    {"8.java", "Java8"},
+    {"14.java", "Java14"},
+    {"17.java", "Java17"},
+    {"21.java", "Java21"},
+    // Others
+    {".pas", "Pascal"},
+    {"2.py", "Python2"},
+    {".py", "Python3"},
 };
 
 struct compile_config {
@@ -336,9 +357,9 @@
 		               "-x", "c++", conf.src);
 	}
 }
-int compile_c(const compile_config &conf) {
+int compile_c(const compile_config &conf, const std::string &std) {
 	std::ostringstream sflags;
-	spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE");
+	spaced_out(sflags, "-lm", "-O2", "-DONLINE_JUDGE", "-std=" + std);
 	for (auto dir : conf.cinclude_dirs) {
 		add_spaced_out(sflags, "-I" + dir);
 	}
@@ -365,7 +386,7 @@
 		"except Exception as e:\n"
 		"    print e\n"
 		"    sys.exit(1)\n";
-	return execute(UOJ_PYTHON2_7, "-E", "-s", "-B", "-O", "-c", escapeshellarg(compiler_code));
+	return execute(UOJ_PYTHON2, "-E", "-s", "-B", "-O", "-c", escapeshellarg(compiler_code));
 }
 int compile_python3(const compile_config &conf) {
 	if (!conf.implementer.empty()) {
@@ -383,7 +404,7 @@
 		"    sys.exit(1)\n";
 	return execute(UOJ_PYTHON3, "-I", "-B", "-O", "-c", escapeshellarg(compiler_code));
 }
-int compile_java(const compile_config &conf, const std::string &jdk) {
+int compile_java(const compile_config &conf, const std::string &version) {
 	if (!conf.implementer.empty()) {
 		throw language_not_supported_error();
 	}
@@ -393,7 +414,8 @@
 		fs::remove_all(conf.name);
 		fs::create_directory(conf.name);
 		fs::copy_file(conf.src, fs::path(conf.name) / (main_class + ".java"));
-		int ret = execute("cd", conf.name, "&&", jdk + "/bin/javac", main_class + ".java");
+		int ret = execute("cd", conf.name, "&&", UOJ_JDK "/bin/javac", "--release", version,
+		                  main_class + ".java");
 		fs::remove(fs::path(conf.name) / (main_class + ".java"));
 		put_class_name_to_file(fs::path(conf.name) / ".main_class_name", main_class);
 		return ret;
@@ -403,7 +425,10 @@
 }
 int compile_pas(const compile_config &conf) {
 	if (conf.implementer.empty()) {
-		return execute(UOJ_FPC, conf.src, "-O2");
+		fs::path src_path(conf.src);
+		fs::path dir = src_path.parent_path();
+		fs::path file = src_path.filename();
+		return execute("cd", dir.string(), "&&", UOJ_FPC, file.string(), "-O2");
 	} else {
 		try {
 			std::string unit_name = get_class_name_from_file(conf.name + ".unit_name");
@@ -422,37 +447,51 @@
 }
 
 int compile(const compile_config &conf) {
-	if ((conf.lang.length() > 0 && conf.lang[0] == 'C') && has_illegal_keywords_in_file(conf.src)) {
+	std::string lang = upgraded_lang(conf.lang);
+
+	if ((lang.length() > 0 && lang[0] == 'C') && has_illegal_keywords_in_file(conf.src)) {
 		std::cerr << "Compile Failed: assembly language detected" << std::endl;
 		return 1;
 	}
 
-	std::string lang = upgraded_lang(conf.lang);
-
-	if (lang == "C++" || lang == "C++14") {
-		return compile_cpp(conf, "c++14");
-	} else if (lang == "C++98") {
+	if (lang == "C++98") {
 		return compile_cpp(conf, "c++98");
 	} else if (lang == "C++03") {
 		return compile_cpp(conf, "c++03");
 	} else if (lang == "C++11") {
 		return compile_cpp(conf, "c++11");
+	} else if (lang == "C++14") {
+		return compile_cpp(conf, "c++14");
 	} else if (lang == "C++17") {
 		return compile_cpp(conf, "c++17");
 	} else if (lang == "C++20") {
 		return compile_cpp(conf, "c++20");
-	} else if (lang == "C") {
-		return compile_c(conf);
-	} else if (lang == "Python2.7") {
+	} else if (lang == "C++23") {
+		return compile_cpp(conf, "c++23");
+	} else if (lang == "C++26") {
+		return compile_cpp(conf, "c++26");
+	} else if (lang == "C89") {
+		return compile_c(conf, "c89");
+	} else if (lang == "C99") {
+		return compile_c(conf, "c99");
+	} else if (lang == "C11") {
+		return compile_c(conf, "c11");
+	} else if (lang == "C17") {
+		return compile_c(conf, "c17");
+	} else if (lang == "C23") {
+		return compile_c(conf, "c23");
+	} else if (lang == "Python2") {
 		return compile_python2_7(conf);
 	} else if (lang == "Python3") {
 		return compile_python3(conf);
 	} else if (lang == "Java8") {
-		return compile_java(conf, UOJ_OPEN_JDK8);
+		return compile_java(conf, "8");
 	} else if (lang == "Java11") {
-		return compile_java(conf, UOJ_OPEN_JDK11);
+		return compile_java(conf, "11");
 	} else if (lang == "Java17") {
-		return compile_java(conf, UOJ_OPEN_JDK17);
+		return compile_java(conf, "17");
+	} else if (lang == "Java21") {
+		return compile_java(conf, "21");
 	} else if (lang == "Pascal") {
 		return compile_pas(conf);
 	} else {
diff -Nrau a/run/run_interaction.cpp b/run/run_interaction.cpp
--- a/run/run_interaction.cpp	2025-10-16 21:08:23.647533681 +0800
+++ b/run/run_interaction.cpp	2025-10-12 21:48:18.090687874 +0800
@@ -12,7 +12,6 @@
 #include <cstring>
 #include <exception>
 #include <iostream>
-#include <set>
 #include <string>
 #include <system_error>
 #include <thread>
diff -Nrau a/run/run_program.cpp b/run/run_program.cpp
--- a/run/run_program.cpp	2025-10-16 21:12:32.820081760 +0800
+++ b/run/run_program.cpp	2025-10-14 22:59:38.955343537 +0800
@@ -1,5 +1,9 @@
+#include <unistd.h>
+
 #include "run_program_sandbox.h"
 
+namespace fs = std::filesystem;
+
 enum RUN_EVENT_TYPE {
 	ET_SKIP,
 	ET_EXIT,
@@ -50,10 +54,10 @@
 
 	switch (key) {
 		case 'T':
-			config->limits.time = round(stod(arg) * 1000) / 1000;
+			config->limits.time = round(std::stod(arg) * 1000) / 1000;
 			break;
 		case 'R':
-			config->limits.real_time = round(stod(arg) * 1000) / 1000;
+			config->limits.real_time = round(std::stod(arg) * 1000) / 1000;
 			break;
 		case 'M':
 			config->limits.memory = atoi(arg);
@@ -150,7 +154,7 @@
 		run_program_config.limits.real_time = run_program_config.limits.time + 2;
 	}
 	run_program_config.limits.stack =
-	    min(run_program_config.limits.stack, run_program_config.limits.memory);
+	    std::min(run_program_config.limits.stack, run_program_config.limits.memory);
 
 	// NOTE: program_name is the full path of the program, not just the file name (but can start
 	// with "./")
@@ -179,13 +183,13 @@
 
 	try {
 		run_program_config.gen_full_args();
-	} catch (exception &e) {
+	} catch (std::exception &e) {
 		// fail to generate full args
 		runp::result(runp::RS_JGF, "error code: GFULARGS").dump_and_exit();
 	}
 }
 
-void set_limit(int r, int rcur, int rmax = -1) {
+void set_limit(int r, ssize_t rcur, ssize_t rmax = -1) {
 	if (rmax == -1) rmax = rcur;
 	struct rlimit l;
 	if (getrlimit(r, &l) == -1) {
@@ -215,9 +219,9 @@
 [[noreturn]] void run_child() {
 	setpgid(0, 0);
 
-	set_limit(RLIMIT_FSIZE, run_program_config.limits.output << 20);
-	set_limit(RLIMIT_STACK, run_program_config.limits.stack << 20);
-	// TODO: use https://man7.org/linux/man-pages/man3/vlimit.3.html to limit virtual memory
+	set_limit(RLIMIT_FSIZE, run_program_config.limits.output << 20ll);
+	set_limit(RLIMIT_STACK, run_program_config.limits.stack << 20ll);
+	set_limit(RLIMIT_AS, (run_program_config.limits.memory + 64ll) << 20ll);
 
 	if (run_program_config.input_file_name != "stdin") {
 		if (freopen(run_program_config.input_file_name.c_str(), "r", stdin) == NULL) {
@@ -251,9 +255,9 @@
 	char *env_path_str = getenv("PATH");
 	char *env_lang_str = getenv("LANG");
 	char *env_shell_str = getenv("SHELL");
-	string env_path = env_path_str ? env_path_str : "";
-	string env_lang = env_lang_str ? env_lang_str : "";
-	string env_shell = env_shell_str ? env_shell_str : "";
+	std::string env_path = env_path_str ? env_path_str : "";
+	std::string env_lang = env_lang_str ? env_lang_str : "";
+	std::string env_shell = env_shell_str ? env_shell_str : "";
 
 	clearenv();
 	setenv("USER", "poor_program", 1);
@@ -303,7 +307,7 @@
 struct timeval start_time;
 struct timeval end_time;
 pid_t rp_timer_pid;
-vector<rp_child_proc> rp_children;
+std::vector<rp_child_proc> rp_children;
 struct rusage *ruse0p = NULL;
 
 bool has_real_TLE() {
@@ -336,11 +340,11 @@
 	rp_children.resize(new_n);
 }
 
-string get_usage_summary(struct rusage *rusep) {
+std::string get_usage_summary(struct rusage *rusep) {
 	struct timeval elapsed;
 	timersub(&end_time, &start_time, &elapsed);
 
-	ostringstream sout;
+	std::ostringstream sout;
 	struct timeval total_cpu;
 	timeradd(&rusep->ru_utime, &rusep->ru_stime, &total_cpu);
 
@@ -373,7 +377,7 @@
 	int stat;
 	while (true) {
 		pid_t pid = wait4(-1, &stat, __WALL, &tmp);
-		// std::cerr << "stop_all: wait " << pid << '\n';
+		// cerr << "stop_all: wait " << pid << endl;
 		if (pid < 0) {
 			if (errno == EINTR) {
 				continue;
@@ -430,7 +434,7 @@
 
 	if (run_program_config.need_show_trace_details) {
 		if (prev_pid != e.pid) {
-			std::cerr << "----------" << e.pid << "----------" << '\n';
+			std::cerr << "----------" << e.pid << "----------\n";
 		}
 		prev_pid = e.pid;
 	}
@@ -567,20 +571,21 @@
 		case ET_SKIP:
 			return;
 		case ET_REAL_TLE:
-			stop_all(runp::result(runp::RS_TLE, "elapsed real time limit exceeded: >"
-			                                        + to_string(run_program_config.limits.real_time)
-			                                        + "s"));
+			stop_all(runp::result(runp::RS_TLE,
+			                      "elapsed real time limit exceeded: >"
+			                          + std::to_string(run_program_config.limits.real_time) + "s"));
 		case ET_USER_CPU_TLE:
 			stop_all(runp::result(runp::RS_TLE, "user CPU time limit exceeded: >"
-			                                        + to_string(run_program_config.limits.time)
+			                                        + std::to_string(run_program_config.limits.time)
 			                                        + "s"));
 		case ET_MLE:
 			stop_all(runp::result(
-			    runp::RS_MLE, "max RSS >" + to_string(run_program_config.limits.memory) + "MB"));
+			    runp::RS_MLE,
+			    "max RSS >" + std::to_string(run_program_config.limits.memory) + "MB"));
 		case ET_OLE:
-			stop_all(runp::result(
-			    runp::RS_OLE,
-			    "output limit exceeded: >" + to_string(run_program_config.limits.output) + "MB"));
+			stop_all(runp::result(runp::RS_OLE,
+			                      "output limit exceeded: >"
+			                          + std::to_string(run_program_config.limits.output) + "MB"));
 		case ET_EXIT:
 			if (run_program_config.need_show_trace_details) {
 				fprintf(stderr, "exit     : %d\n", e.exitcode);
@@ -593,8 +598,9 @@
 				    runp::result(runp::RS_JGF, "error code: CPCMDER2"));  // rp_children mode error
 			} else {
 				if (e.cp == rp_children.data() + 1) {
-					stop_all(runp::result(runp::RS_AC, "exit with code " + to_string(e.exitcode),
-					                      e.usertim, e.usermem, e.exitcode));
+					stop_all(runp::result(runp::RS_AC,
+					                      "exit with code " + std::to_string(e.exitcode), e.usertim,
+					                      e.usermem, e.exitcode));
 				} else {
 					rp_children_del(e.pid);
 				}
@@ -606,8 +612,8 @@
 				fprintf(stderr, "sig exit : %s\n", strsignal(e.sig));
 			}
 			if (e.cp == rp_children.data() + 1) {
-				stop_all(runp::result(runp::RS_RE,
-				                      string("process terminated by signal: ") + strsignal(e.sig)));
+				stop_all(runp::result(
+				    runp::RS_RE, std::string("process terminated by signal: ") + strsignal(e.sig)));
 			} else {
 				rp_children_del(e.pid);
 			}
@@ -675,7 +681,7 @@
 	try {
 		fs::path self_path = fs::read_symlink("/proc/self/exe");
 		runp::run_path = self_path.parent_path();
-	} catch (exception &e) {
+	} catch (std::exception &e) {
 		runp::result(runp::RS_JGF, "error code: PTHFAL2").dump_and_exit();  // path failed
 	}
 
diff -Nrau a/run/run_program_sandbox.h b/run/run_program_sandbox.h
--- a/run/run_program_sandbox.h	2025-10-16 21:08:51.043623948 +0800
+++ b/run/run_program_sandbox.h	2025-10-13 23:00:04.069245118 +0800
@@ -1,3 +1,5 @@
+#pragma once
+
 #include <argp.h>
 #include <fcntl.h>
 #include <seccomp.h>
@@ -10,6 +12,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <cstdint>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
@@ -17,7 +20,6 @@
 #include <iostream>
 #include <map>
 #include <set>
-#include <sstream>
 #include <string>
 #include <vector>
 
@@ -86,7 +88,7 @@
  * a mask that tells seccomp that it should SCMP_ACT_ERRNO(no)
  * when syscall #(mask | no) is called
  * used to implement SCMP_ACT_ERRNO(no) using ptrace:
- *     set the syscall number to mask | no;
+ *     std::set the syscall number to mask | no;
  *     PTRACE_CONT
  *     seccomp performs SCMP_ACT_ERRNO(no)
  */
@@ -98,13 +100,13 @@
     EACCES,  // Permission denied
 };
 
-std::set<std::string> available_program_type_set = {"default", "python2.7", "python3", "java8",
-                                                    "java11",  "java17",    "compiler"};
+std::set<std::string> available_program_type_set = {"default", "python2", "python3", "java",
+                                                    "compiler"};
 
 /*
  * folder program: the program to run is a folder, not a single regular file
  */
-std::set<std::string> folder_program_type_set = {"java8", "java11", "java17"};
+std::set<std::string> folder_program_type_set = {"java"};
 
 std::map<std::string, std::vector<std::pair<int, syscall_info>>> allowed_syscall_list = {
     {"default",
@@ -186,6 +188,7 @@
          {__NR_statfs, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)},
          {__NR_lstat, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_S)},
          {__NR_newfstatat, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_S)},
+         {__NR_statx, syscall_info::with_extra_check(ECT_FILEAT_OP | ECT_FILE_S)},
 
          // kill could be DGS or RE
          {__NR_kill, syscall_info::kill_type_syscall()},
@@ -233,7 +236,7 @@
          {__NR_execve, syscall_info::with_extra_check(ECT_FILE_OP | ECT_FILE_R)},
      }},
 
-    {"python2.7",
+    {"python2",
      {
          {__NR_getdents, syscall_info::unlimited()},
          {__NR_getdents64, syscall_info::unlimited()},
@@ -245,45 +248,10 @@
          {__NR_getdents64, syscall_info::unlimited()},
      }},
 
-    {"java8",
+    {"java",
      {
-         {__NR_clone, syscall_info::with_extra_check(ECT_CLONE_THREAD, 9)},
-         {__NR_clone3, syscall_info::with_extra_check(ECT_CLONE_THREAD, 9)},
-         {__NR_rseq, syscall_info::unlimited()},
-         {__NR_prctl, syscall_info::unlimited()},      // TODO: add extra checks for prctl
-         {__NR_prlimit64, syscall_info::unlimited()},  // TODO: add extra checks for
-                                                       // prlimit64
-
-         {__NR_getdents, syscall_info::unlimited()},
-         {__NR_getdents64, syscall_info::unlimited()},
-
-         {__NR_sched_getaffinity, syscall_info::unlimited()},
-         {__NR_sched_yield, syscall_info::unlimited()},
-     }},
-
-    {"java11",
-     {
-         {__NR_clone, syscall_info::with_extra_check(ECT_CLONE_THREAD, 11)},
-         {__NR_clone3, syscall_info::with_extra_check(ECT_CLONE_THREAD, 11)},
-         {__NR_rseq, syscall_info::unlimited()},
-         {__NR_prctl, syscall_info::unlimited()},      // TODO: add extra checks for prctl
-         {__NR_prlimit64, syscall_info::unlimited()},  // TODO: add extra checks for
-                                                       // prlimit64
-
-         {__NR_getdents, syscall_info::unlimited()},
-         {__NR_getdents64, syscall_info::unlimited()},
-
-         {__NR_sched_getaffinity, syscall_info::unlimited()},
-         {__NR_sched_yield, syscall_info::unlimited()},
-
-         {__NR_nanosleep, syscall_info::unlimited()},
-         {__NR_clock_nanosleep, syscall_info::unlimited()},
-     }},
-
-    {"java17",
-     {
-         {__NR_clone, syscall_info::with_extra_check(ECT_CLONE_THREAD, 13)},
-         {__NR_clone3, syscall_info::with_extra_check(ECT_CLONE_THREAD, 13)},
+         {__NR_clone, syscall_info::with_extra_check(ECT_CLONE_THREAD, 16)},
+         {__NR_clone3, syscall_info::with_extra_check(ECT_CLONE_THREAD, 16)},
          {__NR_rseq, syscall_info::unlimited()},
          {__NR_prctl, syscall_info::unlimited()},      // TODO: add extra checks for prctl
          {__NR_prlimit64, syscall_info::unlimited()},  // TODO: add extra checks for
@@ -392,33 +360,22 @@
 std::map<std::string, std::vector<std::string>> statable_file_name_list = {
     {"default", {}},
 
-    {"python2.7",
+    {"python2",
      {
-         "/usr",
-         "/usr/bin",
-         "/usr/lib",
+         "/usr/",
+         "/usr/bin/",
+         "/usr/lib/",
      }},
 
     {"python3",
      {
-         "/usr",
-         "/usr/bin",
-         "/usr/lib",
-     }},
-
-    {"java8",
-     {
-         "system_root",
-         "/tmp/",
-     }},
-
-    {"java11",
-     {
-         "system_root",
-         "/tmp/",
+         "/usr/",
+         "/usr/bin/",
+         "/usr/lib/",
+         "/etc/python" UOJ_PYTHON3_VERSION "/",
      }},
 
-    {"java17",
+    {"java",
      {
          "system_root",
          "/tmp/",
@@ -442,7 +399,7 @@
          "/proc/sys/vm/",             // for java
      }},
 
-    {"python2.7",
+    {"python2",
      {
          "/etc/python2.7/",
          "/usr/bin/python2.7",
@@ -456,12 +413,12 @@
 
     {"python3",
      {
-         "/etc/python3.10/",
-         "/usr/bin/python3.10",
-         "/usr/lib/python3.10/",
+         "/etc/python/" UOJ_PYTHON3_VERSION,
+         "/usr/bin/python" UOJ_PYTHON3_VERSION,
+         "/usr/lib/python" UOJ_PYTHON3_VERSION "/",
          "/usr/lib/python3/dist-packages/",
-         "/usr/bin/lib/python3.10/",
-         "/usr/local/lib/python3.10/",
+         "/usr/bin/lib/python" UOJ_PYTHON3_VERSION "/",
+         "/usr/local/lib/python" UOJ_PYTHON3_VERSION "/",
          "/usr/bin/pyvenv.cfg",
          "/usr/pyvenv.cfg",
          "/usr/bin/Modules/",
@@ -469,28 +426,14 @@
          "/usr/lib/dist-python",
      }},
 
-    {"java8",
-     {
-         UOJ_OPEN_JDK8 "/",
-         "/sys/fs/cgroup/",
-         "/etc/java-8-openjdk/",
-         "/usr/share/java/",
-     }},
-
-    {"java11",
-     {
-         UOJ_OPEN_JDK11 "/",
-         "/sys/fs/cgroup/",
-         "/etc/java-11-openjdk/",
-         "/usr/share/java/",
-     }},
-
-    {"java17",
+    {"java",
      {
-         UOJ_OPEN_JDK17 "/",
+         UOJ_JDK "/",
          "/sys/fs/cgroup/",
-         "/etc/java-17-openjdk/",
+         "/etc/java-" UOJ_JAVA_VERSION "-openjdk/",
          "/usr/share/java/",
+         "/sys/kernel/mm/hugepages/",
+         "/sys/kernel/mm/transparent_hugepage/",
      }},
 
     {"compiler",
@@ -505,12 +448,14 @@
          "/sys/fs/cgroup/",
          "/proc/",
          "/etc/timezone",
+         "/etc/alternatives/",
+         "/sys/kernel/mm/hugepages/",             // java
+         "/sys/kernel/mm/transparent_hugepage/",  // java
          "/etc/python2.7/",
-         "/etc/python3.10/",
-         "/etc/fpc-3.2.2.cfg",
-         "/etc/java-8-openjdk/",
-         "/etc/java-11-openjdk/",
-         "/etc/java-17-openjdk/",
+         "/etc/python" UOJ_PYTHON3_VERSION "/",
+         "/etc/fpc.cfg",
+         "/etc/fpc-" UOJ_FPC_VERSION ".cfg",
+         "/etc/java-" UOJ_JAVA_VERSION "-openjdk/",
      }}};
 
 std::map<std::string, std::vector<std::string>> writable_file_name_list = {
@@ -518,7 +463,7 @@
      {
          "/dev/null",
 
-         // for java11 and java17
+         // for java
          "/proc/self/coredump_filter",
      }},
 
@@ -964,16 +909,13 @@
     "?432",
     "?433",
     "?434",
-    "?435",
+    "clone3",
     "?436",
     "?437",
     "?438",
     "faccessat2",  // 439
 };
 
-namespace fs = std::filesystem;
-using namespace std;
-
 typedef unsigned long long int reg_val_t;
 #define REG_SYSCALL orig_rax
 #define REG_RET rax
@@ -991,35 +933,58 @@
 
 	struct user_regs_struct reg = {};
 	int syscall = -1;
-	string error;
+	std::string error;
 	bool suspicious = false;
 	bool try_to_create_new_process = false;
 
-	void set_error_for_suspicious(const string &error);
+	void set_error_for_suspicious(const std::string &error);
 	void set_error_for_kill();
 	void soft_ban_syscall(int set_no);
 	bool check_safe_syscall();
-	bool check_file_permission(const string &op, const string &fn, char mode);
+	bool check_file_permission(const std::string &op, const std::string &fn, char mode);
+};
+
+struct clone_args {
+	std::uint64_t flags;        /* Flags bit mask */
+	std::uint64_t pidfd;        /* Where to store PID file descriptor (int *) */
+	std::uint64_t child_tid;    /* Where to store child TID, in child's memory (pid_t *) */
+	std::uint64_t parent_tid;   /* Where to store child TID, in parent's memory (pid_t *) */
+	std::uint64_t exit_signal;  /* Signal to deliver to parent on child termination */
+	std::uint64_t stack;        /* Pointer to lowest byte of stack */
+	std::uint64_t stack_size;   /* Size of stack */
+	std::uint64_t tls;          /* Location of new TLS */
+	std::uint64_t set_tid;      /* Pointer to a pid_t array (since Linux 5.5) */
+	std::uint64_t set_tid_size; /* Number of elements in set_tid (since Linux 5.5) */
+	std::uint64_t cgroup;       /* File descriptor for target cgroup of child (since Linux 5.7) */
 };
 
+void read_memory_from_addr(reg_val_t addr, pid_t pid, void *buf, size_t size) {
+	uint8_t *ptr = (uint8_t *)buf;
+	for (size_t i = 0; i < size; i += sizeof(reg_val_t)) {
+		reg_val_t data = ptrace(PTRACE_PEEKDATA, pid, addr + i, NULL);
+		size_t copy_size = std::min(sizeof(reg_val_t), size - i);
+		memcpy(ptr + i, &data, copy_size);
+	}
+}
+
 const size_t MAX_PATH_LEN = 512;
 const uint64_t MAX_FD_ID = 1 << 20;
 
-const string INVALID_PATH(PATH_MAX + 8, 'X');
-const string EMPTY_PATH_AFTER_FD = "?empty_path_after_fd";
+const std::string INVALID_PATH(PATH_MAX + 8, 'X');
+const std::string EMPTY_PATH_AFTER_FD = "?empty_path_after_fd";
 
 runp::config run_program_config;
 
-set<string> writable_file_name_set;
-set<string> readable_file_name_set;
-set<string> statable_file_name_set;
-set<string> soft_ban_file_name_set;
+std::set<std::string> writable_file_name_set;
+std::set<std::string> readable_file_name_set;
+std::set<std::string> statable_file_name_set;
+std::set<std::string> soft_ban_file_name_set;
 
 syscall_info syscall_info_set[N_SYSCALL];
 
 pid_t get_tgid_from_pid(pid_t pid) {
-	ifstream fin("/proc/" + to_string(pid) + "/status");
-	string key;
+	std::ifstream fin("/proc/" + std::to_string(pid) + "/status");
+	std::string key;
 	while (fin >> key) {
 		if (key == "Tgid:") {
 			pid_t tgid;
@@ -1033,35 +998,37 @@
 	return -1;
 }
 
-bool is_len_valid_path(const string &path) { return !path.empty() && path.size() <= MAX_PATH_LEN; }
+bool is_len_valid_path(const std::string &path) {
+	return !path.empty() && path.size() <= MAX_PATH_LEN;
+}
 
-string path_or_len_invalid(const string &path) {
+std::string path_or_len_invalid(const std::string &path) {
 	return is_len_valid_path(path) ? path : INVALID_PATH;
 }
 
-string basename(const string &path) {
+std::string basename(const std::string &path) {
 	if (!is_len_valid_path(path)) {
 		return INVALID_PATH;
 	}
 	size_t p = path.rfind('/');
-	if (p == string::npos) {
+	if (p == std::string::npos) {
 		return path;
 	} else {
 		return path.substr(p + 1);  // can be empty, e.g., path = "abc/"
 	}
 }
-string dirname(const string &path) {
+std::string dirname(const std::string &path) {
 	if (!is_len_valid_path(path)) {
 		return INVALID_PATH;
 	}
 	size_t p = path.rfind('/');
-	if (p == string::npos) {
+	if (p == std::string::npos) {
 		return INVALID_PATH;
 	} else {
 		return path.substr(0, p);  // can be empty, e.g., path = "/abc"
 	}
 }
-string realpath(const string &path) {
+std::string realpath(const std::string &path) {
 	if (!is_len_valid_path(path)) {
 		return INVALID_PATH;
 	}
@@ -1071,13 +1038,13 @@
 	}
 	return path_or_len_invalid(real);
 }
-string realpath_for_write(const string &path) {
-	string real = realpath(path);
+std::string realpath_for_write(const std::string &path) {
+	std::string real = realpath(path);
 	if (!is_len_valid_path(path)) {
 		return INVALID_PATH;
 	}
 
-	string b = basename(path);
+	std::string b = basename(path);
 	if (!is_len_valid_path(b) || b == "." || b == "..") {
 		return INVALID_PATH;
 	}
@@ -1087,7 +1054,7 @@
 	}
 	return path_or_len_invalid(real + "/" + b);
 }
-string readlink(const string &path) {
+std::string readlink(const std::string &path) {
 	if (!is_len_valid_path(path)) {
 		return INVALID_PATH;
 	}
@@ -1100,7 +1067,7 @@
 		return path_or_len_invalid(buf);
 	}
 }
-string getcwd() {
+std::string getcwd() {
 	char cwd[MAX_PATH_LEN + 1];
 	if (getcwd(cwd, MAX_PATH_LEN) == NULL) {
 		return INVALID_PATH;
@@ -1108,29 +1075,29 @@
 		return path_or_len_invalid(cwd);
 	}
 }
-string getcwdp(pid_t pid) {
-	return realpath("/proc/" + (pid == 0 ? "self" : to_string(pid)) + "/cwd");
+std::string getcwdp(pid_t pid) {
+	return realpath("/proc/" + (pid == 0 ? "self" : std::to_string(pid)) + "/cwd");
 }
-string abspath(const string &path, pid_t pid, int fd = AT_FDCWD) {
+std::string abspath(const std::string &path, pid_t pid, int fd = AT_FDCWD) {
 	static int depth = 0;
 	if (depth == 10 || !is_len_valid_path(path)) {
 		return INVALID_PATH;
 	}
 
-	vector<string> lv;
-	for (string cur = path; is_len_valid_path(cur); cur = dirname(cur)) {
+	std::vector<std::string> lv;
+	for (std::string cur = path; is_len_valid_path(cur); cur = dirname(cur)) {
 		lv.push_back(basename(cur));
 	}
-	reverse(lv.begin(), lv.end());
+	std::reverse(lv.begin(), lv.end());
 
-	string pos;
+	std::string pos;
 	if (path[0] == '/') {
 		pos = "/";
 	} else if (fd == AT_FDCWD) {
 		pos = getcwdp(pid);
 	} else {
 		depth++;
-		pos = abspath("/proc/self/fd/" + to_string(fd), pid);
+		pos = abspath("/proc/self/fd/" + std::to_string(fd), pid);
 		depth--;
 	}
 	if (!is_len_valid_path(pos)) {
@@ -1170,11 +1137,12 @@
 		}
 
 		if (reachable) {
-			string realpos;
+			std::string realpos;
 			if (pos == "/proc/self") {
-				realpos = "/proc/" + to_string(get_tgid_from_pid(pid));
+				realpos = "/proc/" + std::to_string(get_tgid_from_pid(pid));
 			} else if (pos == "/proc/thread-self") {
-				realpos = "/proc/" + to_string(get_tgid_from_pid(pid)) + "/" + to_string(pid);
+				realpos =
+				    "/proc/" + std::to_string(get_tgid_from_pid(pid)) + "/" + std::to_string(pid);
 			} else {
 				if (lstat(pos.c_str(), &stat_buf) < 0) {
 					reachable = false;
@@ -1204,15 +1172,15 @@
 
 	return path_or_len_invalid(pos);
 }
-string getfdp(pid_t pid, int fd) {
+std::string getfdp(pid_t pid, int fd) {
 	if (fd == AT_FDCWD) {
 		return getcwdp(pid);
 	} else {
-		return abspath("/proc/self/fd/" + to_string(fd), pid);
+		return abspath("/proc/self/fd/" + std::to_string(fd), pid);
 	}
 }
 
-inline bool is_in_set_smart(string name, const set<string> &s) {
+inline bool is_in_set_smart(std::string name, const std::set<std::string> &s) {
 	if (name.size() > MAX_PATH_LEN) {
 		return false;
 	}
@@ -1237,32 +1205,32 @@
 	return false;
 }
 
-inline bool is_writable_file(string name) {
+inline bool is_writable_file(std::string name) {
 	if (name == "/") {
 		return writable_file_name_set.count("system_root");
 	}
 	return is_in_set_smart(name, writable_file_name_set);
 }
-inline bool is_readable_file(const string &name) {
+inline bool is_readable_file(const std::string &name) {
 	if (name == "/") {
 		return readable_file_name_set.count("system_root");
 	}
 	return is_in_set_smart(name, readable_file_name_set);
 }
-inline bool is_statable_file(const string &name) {
+inline bool is_statable_file(const std::string &name) {
 	if (name == "/") {
 		return statable_file_name_set.count("system_root");
 	}
 	return is_in_set_smart(name, statable_file_name_set);
 }
-inline bool is_soft_ban_file(const string &name) {
+inline bool is_soft_ban_file(const std::string &name) {
 	if (name == "/") {
 		return soft_ban_file_name_set.count("system_root");
 	}
 	return is_in_set_smart(name, soft_ban_file_name_set);
 }
 
-void add_file_permission(const string &file_name, char mode) {
+void add_file_permission(const std::string &file_name, char mode) {
 	if (file_name.empty()) {
 		return;
 	}
@@ -1276,7 +1244,7 @@
 	if (file_name == "system_root") {
 		return;
 	}
-	for (string name = dirname(file_name); !name.empty(); name = dirname(name)) {
+	for (std::string name = dirname(file_name); !name.empty(); name = dirname(name)) {
 		statable_file_name_set.insert(name);
 	}
 }
@@ -1291,7 +1259,7 @@
 		add_file_permission(realpath(config.program_name), 'r');
 	}
 
-	vector<string> loads;
+	std::vector<std::string> loads;
 	loads.push_back("default");
 	if (config.allow_proc) {
 		loads.push_back("allow_proc");
@@ -1300,7 +1268,7 @@
 		loads.push_back(config.type);
 	}
 
-	for (string type : loads) {
+	for (std::string type : loads) {
 		if (allowed_syscall_list.count(type)) {
 			for (const auto &kv : allowed_syscall_list[type]) {
 				syscall_info_set[kv.first] = kv.second;
@@ -1335,7 +1303,7 @@
 		add_file_permission(name, 'w');
 	}
 
-	if (config.type == "python2.7" || config.type == "python3") {
+	if (config.type == "python2" || config.type == "python3") {
 		soft_ban_file_name_set.insert(dirname(realpath(config.program_name)) + "/__pycode__/");
 	} else if (config.type == "compiler") {
 		add_file_permission(config.work_path + "/", 'w');
@@ -1345,7 +1313,7 @@
 	statable_file_name_set.insert(readable_file_name_set.begin(), readable_file_name_set.end());
 }
 
-string read_string_from_addr(reg_val_t addr, pid_t pid) {
+std::string read_string_from_addr(reg_val_t addr, pid_t pid) {
 	int max_len = MAX_PATH_LEN + sizeof(reg_val_t);
 	char res[max_len + 1], *ptr = res;
 	while (ptr != res + max_len) {
@@ -1359,21 +1327,21 @@
 	res[max_len] = 0;
 	return res;
 }
-string read_abspath_from_addr(reg_val_t addr, pid_t pid) {
-	string p = read_string_from_addr(addr, pid);
-	string a = abspath(p, pid);
+std::string read_abspath_from_addr(reg_val_t addr, pid_t pid) {
+	std::string p = read_string_from_addr(addr, pid);
+	std::string a = abspath(p, pid);
 	if (run_program_config.need_show_trace_details) {
 		fprintf(stderr, "path     : %s -> %s\n", p.c_str(),
 		        is_len_valid_path(a) ? a.c_str() : "INVALID!");
 	}
 	return a;
 }
-string read_abspath_from_fd_and_addr(reg_val_t fd, reg_val_t addr, pid_t pid) {
+std::string read_abspath_from_fd_and_addr(reg_val_t fd, reg_val_t addr, pid_t pid) {
 	if (fd > MAX_FD_ID && (int)fd != AT_FDCWD) {
 		return INVALID_PATH;
 	}
-	string p = read_string_from_addr(addr, pid);
-	string a;
+	std::string p = read_string_from_addr(addr, pid);
+	std::string a;
 	if (p.empty()) {
 		// this case is tricky
 		// if p is empty, in the following cases, Linux will understand the path as the path of fd:
@@ -1402,7 +1370,7 @@
 	try {
 		for (int no : supported_soft_ban_errno_list) {
 			if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(no), SYSCALL_SOFT_BAN_MASK | no, 0) < 0) {
-				throw system_error();
+				throw std::system_error();
 			}
 		}
 
@@ -1410,17 +1378,17 @@
 			if (syscall_info_set[i].extra_check == ECT_NONE) {
 				if (syscall_info_set[i].should_soft_ban) {
 					if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), i, 0) < 0) {
-						throw system_error();
+						throw std::system_error();
 					}
 				} else {
 					if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, i, 0) < 0) {
-						throw system_error();
+						throw std::system_error();
 					}
 				}
 			}
 		}
 		seccomp_load(ctx);
-	} catch (system_error &e) {
+	} catch (std::system_error &e) {
 		seccomp_release(ctx);
 		return false;
 	}
@@ -1428,7 +1396,7 @@
 	return true;
 }
 
-void rp_child_proc::set_error_for_suspicious(const string &error) {
+void rp_child_proc::set_error_for_suspicious(const std::string &error) {
 	this->suspicious = true;
 	this->error = "suspicious system call invoked: " + error;
 }
@@ -1438,7 +1406,7 @@
 	reg_val_t sig = this->syscall == __NR_tgkill ? this->reg.REG_ARG2 : this->reg.REG_ARG1;
 	this->error = "signal sent via " + syscall_name[this->syscall] + ": ";
 	if (sig != (unsigned)sig) {
-		this->error += "Unknown signal " + to_string(sig);
+		this->error += "Unknown signal " + std::to_string(sig);
 	} else {
 		this->error += strsignal((int)sig);
 	}
@@ -1449,8 +1417,8 @@
 	ptrace(PTRACE_SETREGS, pid, NULL, &this->reg);
 }
 
-bool rp_child_proc::check_file_permission(const string &op, const string &fn, char mode) {
-	string real_fn;
+bool rp_child_proc::check_file_permission(const std::string &op, const std::string &fn, char mode) {
+	std::string real_fn;
 	if (!fn.empty()) {
 		real_fn = mode == 'w' ? realpath_for_write(fn) : realpath(fn);
 	}
@@ -1461,7 +1429,7 @@
 		return true;
 	}
 
-	string path_proc_self = "/proc/" + to_string(get_tgid_from_pid(this->pid));
+	std::string path_proc_self = "/proc/" + std::to_string(get_tgid_from_pid(this->pid));
 	if (real_fn.compare(0, path_proc_self.size() + 1, path_proc_self + "/") == 0) {
 		real_fn = "/proc/self" + real_fn.substr(path_proc_self.size());
 	} else if (real_fn == path_proc_self) {
@@ -1492,11 +1460,11 @@
 		fprintf(stderr, "check file permission %s : %s\n", op.c_str(), real_fn.c_str());
 		fprintf(stderr, "[readable]\n");
 		for (auto s : readable_file_name_set) {
-			cerr << s << endl;
+			std::cerr << s << '\n';
 		}
 		fprintf(stderr, "[writable]\n");
 		for (auto s : writable_file_name_set) {
-			cerr << s << endl;
+			std::cerr << s << '\n';
 		}
 	}
 
@@ -1517,12 +1485,12 @@
 		if (run_program_config.need_show_trace_details) {
 			fprintf(stderr, "informal syscall  %d\n", cur_instruction);
 		}
-		this->set_error_for_suspicious("incorrect opcode " + to_string(cur_instruction));
+		this->set_error_for_suspicious("incorrect opcode " + std::to_string(cur_instruction));
 		return false;
 	}
 
 	if (0 > (long long int)reg.REG_SYSCALL || (long long int)reg.REG_SYSCALL >= N_SYSCALL) {
-		this->set_error_for_suspicious(to_string(reg.REG_SYSCALL));
+		this->set_error_for_suspicious(std::to_string(reg.REG_SYSCALL));
 		return false;
 	}
 	syscall = (int)reg.REG_SYSCALL;
@@ -1560,14 +1528,14 @@
 	}
 
 	if (cursc.extra_check & ECT_FILE_OP) {
-		string fn;
+		std::string fn;
 		if (cursc.extra_check & ECT_END_AT) {
 			fn = read_abspath_from_fd_and_addr(reg.REG_ARG0, reg.REG_ARG1, pid);
 		} else {
 			fn = read_abspath_from_addr(reg.REG_ARG0, pid);
 		}
 
-		string textop = syscall_name[syscall];
+		std::string textop = syscall_name[syscall];
 		char mode = 'w';
 		if (cursc.extra_check & ECT_CHECK_OPEN_FLAGS) {
 			reg_val_t flags = cursc.extra_check & ECT_END_AT ? reg.REG_ARG2 : reg.REG_ARG1;
@@ -1630,14 +1598,27 @@
 	}
 
 	if (cursc.extra_check & ECT_CLONE_THREAD) {
-		reg_val_t flags = reg.REG_ARG0;
+		reg_val_t flags;
+		if (syscall == __NR_clone) {
+			flags = reg.REG_ARG0;
+		} else if (syscall == __NR_clone3) {
+			struct clone_args args;
+			read_memory_from_addr(reg.REG_ARG0, pid, &args, sizeof(args));
+			flags = args.flags;
+		} else {
+			// Should not happen if syscalls are configured correctly
+			this->set_error_for_suspicious("clone/clone3 check on non-clone syscall");
+			return false;
+		}
+
 		if (!(flags & CLONE_THREAD)) {
 			this->set_error_for_suspicious("intended to create a new process");
 			return false;
 		}
-		auto standard_flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND;
-		standard_flags |= CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
-		if (!(flags & standard_flags)) {
+		reg_val_t standard_flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM
+		                           | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
+
+		if ((flags & standard_flags) != standard_flags) {
 			this->set_error_for_suspicious("intended to create a non-standard thread");
 			return false;
 		}

@renbaoshuo
Copy link
Member

可否将最近的关于「MathJax」和「Prism」的提交暂且撤下?这个 PR 有点过大了,不要再向里面塞其他东西了,不然 review 心智负担会愈来愈大。

@aberter0x3f
Copy link
Author

Reverted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants