-
Notifications
You must be signed in to change notification settings - Fork 2
Anticheat #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Anticheat #5
Changes from all commits
26617d7
46e8d51
b1733f8
d21da6d
6cbaaac
be2667b
5f2a344
c9bce92
1604a90
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,9 +10,108 @@ | |
| #include <QLineEdit> | ||
| #include <QTextStream> | ||
| #include <QUrl> | ||
| #include <QProcess> | ||
| #include <QProcessEnvironment> | ||
| #include <QFileInfo> | ||
|
|
||
| #include <fcntl.h> | ||
| #include <unistd.h> | ||
| #include <sys/ioctl.h> | ||
| #include <linux/vt.h> | ||
| #include <linux/kd.h> | ||
| #include <signal.h> | ||
| namespace { | ||
|
|
||
| int g_tty0_fd = -1; | ||
| int g_new_tty_fd = -1; | ||
| int g_original_vt = -1; | ||
|
|
||
| void cleanup_vt_and_exit() { | ||
| if (g_new_tty_fd >= 0) { | ||
| ioctl(g_new_tty_fd, KDSETMODE, KD_TEXT); | ||
| } | ||
| if (g_tty0_fd >= 0) { | ||
| int target_vt = (g_original_vt > 0) ? g_original_vt : 1; | ||
| ioctl(g_tty0_fd, VT_ACTIVATE, target_vt); | ||
| ioctl(g_tty0_fd, VT_WAITACTIVE, target_vt); | ||
| } | ||
| if (g_new_tty_fd >= 0) close(g_new_tty_fd); | ||
| if (g_tty0_fd >= 0) close(g_tty0_fd); | ||
| } | ||
|
|
||
| void barebones_sig_handler(int signum) { | ||
| cleanup_vt_and_exit(); | ||
| if (signum == SIGSEGV || signum == SIGABRT || signum == SIGFPE || signum == SIGILL) { | ||
| signal(signum, SIG_DFL); | ||
| raise(signum); | ||
| } else { | ||
| _exit(1); | ||
| } | ||
| } | ||
|
|
||
| bool setup_barebones_vt() { | ||
| signal(SIGINT, barebones_sig_handler); | ||
| signal(SIGTERM, barebones_sig_handler); | ||
| signal(SIGSEGV, barebones_sig_handler); | ||
| signal(SIGABRT, barebones_sig_handler); | ||
| signal(SIGILL, barebones_sig_handler); | ||
| signal(SIGFPE, barebones_sig_handler); | ||
|
|
||
| g_tty0_fd = open("/dev/tty0", O_RDWR); | ||
| if (g_tty0_fd < 0) { | ||
| qWarning() << "Could not open /dev/tty0. Did you run the app as root?"; | ||
| return false; | ||
| } | ||
|
|
||
| int free_vt = -1; | ||
| if (ioctl(g_tty0_fd, VT_OPENQRY, &free_vt) < 0 || free_vt == -1) { | ||
| qWarning() << "Could not find a free VT."; | ||
| close(g_tty0_fd); | ||
| g_tty0_fd = -1; | ||
| return false; | ||
| } | ||
|
|
||
| char vt_name[20]; | ||
| snprintf(vt_name, sizeof(vt_name), "/dev/tty%d", free_vt); | ||
| g_new_tty_fd = open(vt_name, O_RDWR); | ||
| if (g_new_tty_fd < 0) { | ||
| qWarning() << "Could not open VT" << vt_name; | ||
| close(g_tty0_fd); | ||
| g_tty0_fd = -1; | ||
| return false; | ||
| } | ||
|
|
||
| struct vt_stat vts; | ||
| if (ioctl(g_tty0_fd, VT_GETSTATE, &vts) == 0) { | ||
| g_original_vt = vts.v_active; | ||
| } | ||
|
|
||
| if (ioctl(g_tty0_fd, VT_ACTIVATE, free_vt) < 0 || | ||
| ioctl(g_tty0_fd, VT_WAITACTIVE, free_vt) < 0) { | ||
| qWarning() << "Could not switch to VT" << free_vt; | ||
| close(g_new_tty_fd); | ||
| g_new_tty_fd = -1; | ||
| close(g_tty0_fd); | ||
| g_tty0_fd = -1; | ||
| return false; | ||
| } | ||
|
|
||
| if (ioctl(g_new_tty_fd, KDSETMODE, KD_GRAPHICS) < 0) { | ||
| qWarning() << "Error setting graphics mode on VT" << free_vt; | ||
| int target_vt = (g_original_vt > 0) ? g_original_vt : 1; | ||
| ioctl(g_tty0_fd, VT_ACTIVATE, target_vt); | ||
| ioctl(g_tty0_fd, VT_WAITACTIVE, target_vt); | ||
| close(g_new_tty_fd); | ||
| g_new_tty_fd = -1; | ||
| close(g_tty0_fd); | ||
| g_tty0_fd = -1; | ||
| return false; | ||
| } | ||
|
|
||
| atexit(cleanup_vt_and_exit); | ||
| return true; | ||
| } | ||
|
|
||
| QString findConfigPath(int argc, char *argv[]) | ||
| { | ||
| for (int index = 1; index < argc; ++index) { | ||
|
|
@@ -37,6 +136,18 @@ void applyEarlyEnvironment(int argc, char *argv[]) | |
| settings = loaded.settings; | ||
| } | ||
| } | ||
| for (int i = 0; i < argc; ++i) { // we have to use a loop because the command line parser was not yet loaded. | ||
| if (QString::fromLocal8Bit(argv[i]) == QStringLiteral("--anti-cheat")) { | ||
| if (!setup_barebones_vt()) { | ||
| qCritical() << "Anti-cheat VT setup failed; aborting."; | ||
| _exit(1); | ||
| } | ||
| qputenv("QT_QPA_PLATFORM", "linuxfb"); | ||
| qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--no-sandbox"); | ||
| qputenv("QT_QUICK_BACKEND", "software"); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| seb::browser::applyWebEngineEnvironment(settings); | ||
| } | ||
|
|
@@ -73,7 +184,9 @@ void applyCommandLineOverrides(const QCommandLineParser &parser, seb::SebSetting | |
| if (parser.isSet("windowed")) { | ||
| settings.browser.mainWindow.fullScreenMode = false; | ||
| } | ||
|
|
||
| if (parser.isSet("fullscreen")){ | ||
| settings.browser.mainWindow.fullScreenMode = true; | ||
| } | ||
| if (parser.isSet("always-on-top")) { | ||
| settings.browser.mainWindow.alwaysOnTop = true; | ||
| } | ||
|
|
@@ -94,7 +207,8 @@ int main(int argc, char *argv[]) | |
| applyEarlyEnvironment(argc, argv); | ||
|
|
||
| QApplication app(argc, argv); | ||
| const QIcon appIcon(QStringLiteral(":/assets/icons/safe-exam-browser.png")); | ||
|
|
||
| const QIcon appIcon(QStringLiteral(":/assets/icons/safe-exam-browser.png")); | ||
| app.setWindowIcon(appIcon); | ||
| app.setDesktopFileName(QStringLiteral("safe-exam-browser")); | ||
| QCoreApplication::setApplicationName(QStringLiteral("Safe Exam Browser")); | ||
|
|
@@ -127,13 +241,17 @@ int main(int argc, char *argv[]) | |
| QStringLiteral("allow-devtools"), | ||
| QStringLiteral("Enable the developer tools shortcut (F12)."))); | ||
| parser.addOption(QCommandLineOption(QStringLiteral("windowed"), QStringLiteral("Force the main window to stay windowed."))); | ||
| parser.addOption(QCommandLineOption(QStringLiteral("fullscreen"), QStringLiteral("Force the main window to be fullscreen."))); | ||
| parser.addOption(QCommandLineOption(QStringLiteral("always-on-top"), QStringLiteral("Keep the main window above other windows."))); | ||
| parser.addOption(QCommandLineOption( | ||
| QStringLiteral("disable-minimize"), | ||
| QStringLiteral("Prevent minimizing the main exam window."))); | ||
| parser.addOption(QCommandLineOption( | ||
| QStringLiteral("disable-quit"), | ||
| QStringLiteral("Disable manual termination even if the configuration allows it."))); | ||
| parser.addOption(QCommandLineOption( | ||
| QStringLiteral("anti-cheat"), | ||
| QStringLiteral("Enable anticheat mode."))); | ||
|
|
||
| parser.process(app); | ||
|
|
||
|
|
@@ -144,9 +262,18 @@ int main(int argc, char *argv[]) | |
| ? parser.value("config") | ||
| : (parser.positionalArguments().isEmpty() ? QString() : parser.positionalArguments().constFirst()); | ||
|
|
||
| QString userPassword; | ||
| bool usedPassword = false; | ||
|
|
||
| QStringList warnings; | ||
| if (!resource.isEmpty()) { | ||
| const seb::ResourceLoadResult loaded = seb::loadSettingsFromResource(resource, [](bool hashed) { | ||
| const seb::ResourceLoadResult loaded = seb::loadSettingsFromResource(resource, [&userPassword, &usedPassword](bool hashed) { | ||
| if (qEnvironmentVariableIsSet("SEB_PASSWORD")) { | ||
| userPassword = QString::fromUtf8(qgetenv("SEB_PASSWORD")); | ||
| usedPassword = true; | ||
| return userPassword; | ||
| } | ||
|
|
||
| bool accepted = false; | ||
| const QString password = QInputDialog::getText( | ||
| nullptr, | ||
|
|
@@ -157,6 +284,10 @@ int main(int argc, char *argv[]) | |
| QLineEdit::Password, | ||
| QString(), | ||
| &accepted); | ||
| if (accepted) { | ||
| userPassword = password; | ||
| usedPassword = true; | ||
| } | ||
| return accepted ? password : QString(); | ||
| }); | ||
| if (!loaded.ok) { | ||
|
|
@@ -170,6 +301,35 @@ int main(int argc, char *argv[]) | |
|
|
||
| applyCommandLineOverrides(parser, settings); | ||
|
|
||
| if (!parser.isSet("anti-cheat")) { | ||
| if (settings.browser.mainWindow.fullScreenMode && settings.browser.mainWindow.alwaysOnTop) { | ||
incomplete-tree marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| QStringList args = QCoreApplication::arguments(); | ||
| args.removeFirst(); // Remove executable path | ||
| if (!args.contains(QStringLiteral("--anti-cheat"))) { | ||
| args.prepend(QStringLiteral("--anti-cheat")); | ||
| } | ||
|
|
||
| QProcess child; | ||
| child.setProgram(QStringLiteral("pkexec")); | ||
|
|
||
| QStringList pkexecArgs; | ||
| pkexecArgs << QStringLiteral("--keep-cwd"); | ||
| if (usedPassword) { | ||
incomplete-tree marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| pkexecArgs << QStringLiteral("env"); | ||
| pkexecArgs << (QStringLiteral("SEB_PASSWORD=") + userPassword); | ||
|
Comment on lines
+318
to
+319
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an encrypted Useful? React with 👍 / 👎. |
||
| } | ||
| pkexecArgs << QCoreApplication::applicationFilePath(); | ||
| pkexecArgs << args; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| child.setArguments(pkexecArgs); | ||
|
|
||
| if (child.startDetached()) { | ||
| return 0; | ||
| } | ||
| return 1; | ||
| } | ||
| } | ||
|
|
||
| AppController controller; | ||
| QString launchError; | ||
| if (!controller.launchResolved(settings, warnings, &launchError)) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't pass the SEB password through
pkexec env.The
pkexec env SEB_PASSWORD=...handoff exposes the password in the detached process command line and then keeps it in the child environment for the rest of the session. Please switch this back to a one-shot transport such as the previousSEB_PASSWORD_FILEtemp-file flow instead of argv/environment transport.Based on learnings: in
src/main.cpp, the prior anticheat re-launch block intentionally usedQTemporaryFile* tempFilewithSEB_PASSWORD_FILE, and the child deleted that file after reading it.Also applies to: 293-296
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The password isnt that secret...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@incomplete-tree, understood — if the SEB password is not considered sensitive in your deployment context, the environment-variable transport is simpler. The main residual risk worth being aware of is that environment variables of a running process can be read by other processes owned by the same user (e.g. via/proc/<pid>/environ), and they persist for the lifetime of the child process rather than being consumed immediately. But if that's acceptable for your threat model, no objection.✏️ Learnings added
🧠 Learnings used