按源保护 PythonPlugin TOPLEVEL 路径边界

This commit is contained in:
cnc
2026-06-06 05:26:33 +08:00
parent 66ea34a76d
commit 4adcd82b74
4 changed files with 113 additions and 2 deletions

View File

@@ -1,7 +1,12 @@
#include "pythonplugin/python_plugin.hh"
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits.h>
#include <string>
#include <unistd.h>
// LinuxCNC source basis: src/emc/pythonplugin/python_plugin.cc provides the
// native PythonPlugin implementation called by RS274 Python/remap paths.
@@ -11,6 +16,61 @@ __attribute__((weak)) std::string handle_pyerror() {
PythonPlugin *python_plugin = nullptr;
namespace {
std::string trim_ini_value(std::string value) {
const auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
});
const auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
}).base();
if (first >= last) {
return {};
}
return std::string(first, last);
}
bool find_ini_value(const char *iniFilename,
const char *section,
const char *key,
std::string &value) {
FILE *file = std::fopen(iniFilename, "r");
if (!file) {
return false;
}
bool in_section = false;
char line[PATH_MAX + 256];
while (std::fgets(line, sizeof(line), file)) {
std::string text = trim_ini_value(line);
if (text.empty() || text[0] == ';' || text[0] == '#') {
continue;
}
if (text.front() == '[' && text.back() == ']') {
in_section = text.substr(1, text.size() - 2) == section;
continue;
}
if (!in_section) {
continue;
}
const auto equals = text.find('=');
if (equals == std::string::npos) {
continue;
}
if (trim_ini_value(text.substr(0, equals)) == key) {
value = trim_ini_value(text.substr(equals + 1));
std::fclose(file);
return true;
}
}
std::fclose(file);
return false;
}
} // namespace
PythonPlugin *PythonPlugin::instantiate(struct _inittab *) {
static PythonPlugin plugin;
python_plugin = &plugin;
@@ -42,6 +102,19 @@ int PythonPlugin::configure(const char *iniFilename, const char *section) {
}
std::fclose(file);
std::string toplevel;
if (find_ini_value(iniFilename, section, "TOPLEVEL", toplevel)) {
char resolved_path[PATH_MAX];
// LinuxCNC source basis: python_plugin.cc configure() expands and
// resolves [PYTHON] TOPLEVEL before initialize(); a missing path
// returns PLUGIN_BAD_PATH before Python runtime status is considered.
if (realpath(toplevel.c_str(), resolved_path) == nullptr) {
error_msg = "bad path";
status = PLUGIN_BAD_PATH;
return status;
}
}
return initialize();
}

View File

@@ -60,6 +60,32 @@ int main() {
std::fputs("[PYTHON]\n", file);
std::fclose(file);
}
const char *bad_path_ini = "/tmp/cnc_sim_python_plugin_probe_bad_path.ini";
{
FILE *file = std::fopen(bad_path_ini, "w");
if (!file) {
std::remove(ini);
return 33;
}
std::fputs("[PYTHON]\nTOPLEVEL = /tmp/cnc_sim_missing_python_toplevel.py\n", file);
std::fclose(file);
}
if (plugin->configure(bad_path_ini, "PYTHON") != PLUGIN_BAD_PATH) {
std::remove(ini);
std::remove(bad_path_ini);
return 34;
}
if (plugin->usable()) {
std::remove(ini);
std::remove(bad_path_ini);
return 35;
}
if (plugin->last_errmsg() != "bad path") {
std::remove(ini);
std::remove(bad_path_ini);
return 36;
}
std::remove(bad_path_ini);
setenv("INI_FILE_NAME", ini, 1);
if (plugin->configure(nullptr, "PYTHON") != PLUGIN_PYTHON_NOT_INITIALIZED) {
std::remove(ini);