MCPcopy Create free account
hub / github.com/FreeCAD/FreeCAD / isAllowedModule

Function isAllowedModule

src/App/PropertyPythonObject.cpp:89–189  ·  view source on GitHub ↗

* @brief Check whether a module import should be allowed during document restore. * * Modules already in @c sys.modules are permitted -- they were loaded by FreeCAD core or addons * during normal startup. For modules not yet loaded we use @c importlib.util.find_spec() to * locate where the module would come from without executing it, then verify that path is under * a FreeCAD module dir

Source from the content-addressed store, hash-verified

87 * @return @c true if the module is allowed, @c false otherwise.
88 */
89bool isAllowedModule(const std::string& moduleName)
90{
91 Py::Dict sysModules(PyImport_GetModuleDict());
92 if (sysModules.isNone()) {
93 return false;
94 }
95
96 // 1) Already loaded? Must be safe.
97 if (sysModules.hasKey(moduleName)) {
98 return true;
99 }
100
101 // 2) Is it *in* an already loaded module? Safe.
102 std::string::size_type dot = moduleName.find('.');
103 if (dot != std::string::npos) {
104 std::string topLevel = moduleName.substr(0, dot);
105 if (sysModules.hasKey(topLevel)) {
106 return true;
107 }
108 }
109
110 // 3) The complicated path. Use importlib.util.find_spec() to find the origin of the module,
111 // being careful to NOT load it (which is the code-execution vulnerability we're trying to
112 // avoid in the first place). See if it's in one of our "safe" paths, and if it is, allow it.
113 // Safe paths are a few subdirectories we recognize in the set "home", "resource", and
114 // "userData" paths. Don't allow modules from outside of these directories. Not 100% mitigation,
115 // but it's better than nothing.
116 PyObject* importlibUtil = PyImport_ImportModule("importlib.util");
117 if (!importlibUtil) {
118 PyErr_Clear();
119 return false;
120 }
121 Py::Module importlib(importlibUtil, true);
122 Py::Callable findSpec(importlib.getAttr("find_spec"));
123
124 // FreeCAD adds each workbench directory to sys.path individually (e.g. .../Mod/Assembly/),
125 // so a module stored as "Assembly.JointObject" in the FCStd is actually importable as just
126 // "JointObject". Try the full name first, then the part after the first dot.
127 std::vector<std::string> namesToTry = {moduleName};
128 if (dot != std::string::npos) {
129 namesToTry.push_back(moduleName.substr(dot + 1));
130 }
131 Py::Object spec;
132 for (const std::string& name : namesToTry) {
133 Py::Tuple args(1);
134 args.setItem(0, Py::String(name));
135 try {
136 spec = findSpec.apply(args);
137 }
138 catch (Py::Exception&) {
139 PyErr_Clear();
140 continue;
141 }
142 if (!spec.isNone()) {
143 break;
144 }
145 }
146 if (spec.isNone()) {

Callers 1

RestoreMethod · 0.85

Calls 11

isUnderDirectoryFunction · 0.85
isNoneMethod · 0.80
substrMethod · 0.80
as_std_stringMethod · 0.80
StringClass · 0.70
findMethod · 0.45
push_backMethod · 0.45
applyMethod · 0.45
sizeMethod · 0.45
typeMethod · 0.45
getItemMethod · 0.45

Tested by

no test coverage detected