按源保护 PythonPlugin TOPLEVEL 路径边界
This commit is contained in:
@@ -92,10 +92,16 @@ grep -F 'return vsnprintf(buffer, size, fmt, args);' \
|
||||
"$linuxcnc_root/src/rtapi/uspace_common.h" >/dev/null
|
||||
grep -F 'class PythonPlugin' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
|
||||
grep -F 'PLUGIN_PYTHON_NOT_INITIALIZED = -12,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
|
||||
grep -F 'PLUGIN_BAD_PATH = -5,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
|
||||
grep -F 'PLUGIN_NO_CALLABLE = 1,' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
|
||||
grep -F 'PLUGIN_EXCEPTION = 2' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.hh" >/dev/null
|
||||
grep -F 'PythonPlugin::instantiate' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
|
||||
grep -F 'if (status < PLUGIN_OK)' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
|
||||
grep -F 'if (auto inistring = inifile.findString("TOPLEVEL", section)) {' \
|
||||
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
|
||||
grep -F 'if (realpath(toplevel, real_path) == NULL) {' \
|
||||
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
|
||||
grep -F 'status = PLUGIN_BAD_PATH;' "$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
|
||||
grep -F 'retval = bp::exec_file(cmd, main_namespace, main_namespace);' \
|
||||
"$linuxcnc_root/src/emc/pythonplugin/python_plugin.cc" >/dev/null
|
||||
grep -F 'retval = bp::exec(cmd, main_namespace, main_namespace);' \
|
||||
@@ -317,6 +323,9 @@ for forbidden in [
|
||||
raise SystemExit(f"PythonPlugin wasm shim must not execute Python: {forbidden}")
|
||||
for needle in [
|
||||
"python disabled in wasm-safe probe",
|
||||
'find_ini_value(iniFilename, section, "TOPLEVEL", toplevel)',
|
||||
"realpath(toplevel.c_str(), resolved_path) == nullptr",
|
||||
"status = PLUGIN_BAD_PATH;",
|
||||
"status = PLUGIN_PYTHON_NOT_INITIALIZED;",
|
||||
"return PLUGIN_NO_CALLABLE;",
|
||||
"status = PLUGIN_EXCEPTION;",
|
||||
@@ -327,6 +336,7 @@ for needle in [
|
||||
for needle in [
|
||||
"PLUGIN_NO_SECTION",
|
||||
"PLUGIN_BAD_INIFILE",
|
||||
"PLUGIN_BAD_PATH",
|
||||
"PLUGIN_PYTHON_NOT_INITIALIZED",
|
||||
"PLUGIN_NO_CALLABLE",
|
||||
"PLUGIN_EXCEPTION",
|
||||
@@ -407,6 +417,7 @@ for phrase in [
|
||||
"must not execute `bp::exec`, `bp::exec_file`, or",
|
||||
"inactive-DB return values",
|
||||
"negative-status short-circuit behavior",
|
||||
"preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge",
|
||||
"LinuxCNC `rcs_print_error` split entry points",
|
||||
"without native print routing",
|
||||
"LinuxCNC userspace `rtapi_snprintf()` formatting",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -61,8 +61,9 @@ only; it must not add CNC behavior and must not expand the temporary smoke parse
|
||||
`python_plugin.cc` link expectations; it must not call into CPython runtime.
|
||||
- `core/wasm_shims/pythonplugin/python_plugin.cc` must preserve LinuxCNC
|
||||
`PythonPlugin` status edges for `PLUGIN_NO_SECTION`, `PLUGIN_BAD_INIFILE`,
|
||||
`PLUGIN_PYTHON_NOT_INITIALIZED`, `PLUGIN_NO_CALLABLE`, and
|
||||
`PLUGIN_EXCEPTION` while keeping Python execution disabled.
|
||||
`PLUGIN_BAD_PATH`, `PLUGIN_PYTHON_NOT_INITIALIZED`, `PLUGIN_NO_CALLABLE`,
|
||||
and `PLUGIN_EXCEPTION` while keeping Python execution disabled.
|
||||
- The PythonPlugin shim must preserve the `TOPLEVEL` missing-path `PLUGIN_BAD_PATH` edge from `src/emc/pythonplugin/python_plugin.cc` before reporting the browser-safe disabled Python runtime status.
|
||||
- The PythonPlugin shim must not execute `bp::exec`, `bp::exec_file`, or real
|
||||
Python callable dispatch; `PyObject_Call` is an inert C API symbol only.
|
||||
Browser Python execution remains blocked until implemented as a source-backed
|
||||
|
||||
Reference in New Issue
Block a user