From e0341a65ebc9b9bf6e581902d5f145ca3a3ef2de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20Domozi?= Date: Sun, 1 Mar 2026 11:48:11 +0100 Subject: [PATCH] Fix heap use-after-free in shared object loading The bug was first observed in the CI pipeline, which occasionally failed with a free(): invalid pointer error. Building the project with AddressSanitizer enabled revealed a heap use-after-free issue. It was introduced by Python plugin PR (commit f69d6db81f0a20e7167889d5c43ba8c148aa467d), which modified how plugin shared objects are loaded. If both cppparser and cxxmetricsparser plugins are loaded with dlopen using the RTLD_GLOBAL flag, the global objects in these shared libraries conflict on unload, causing a use-after-free error. As a solution, we reverted to the previous behavior: all shared objects are loaded with RTLD_NOW by default, and only the pythonparser shared object is loaded with RTLD_GLOBAL. --- parser/src/pluginhandler.cpp | 11 ++++++++++- util/include/util/dynamiclibrary.h | 2 +- util/src/dynamiclibrary.cpp | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/parser/src/pluginhandler.cpp b/parser/src/pluginhandler.cpp index e656e31de..248075588 100644 --- a/parser/src/pluginhandler.cpp +++ b/parser/src/pluginhandler.cpp @@ -52,8 +52,17 @@ void PluginHandler::loadPlugins(std::vector& skipParserList_) skipParserList_.end()) { std::string dynamicLibraryPath = dirIter->path().string(); + int dlopenFlags = RTLD_NOW; + if (filename == "pythonparser") + { + // RTLD_GLOBAL: + // The symbols defined by this shared object will be made available for + // symbol resolution of subsequently loaded shared objects. + dlopenFlags |= RTLD_GLOBAL; + } + _dynamicLibraries[filename] = util::DynamicLibraryPtr( - new util::DynamicLibrary(dynamicLibraryPath)); + new util::DynamicLibrary(dynamicLibraryPath, dlopenFlags)); } else { diff --git a/util/include/util/dynamiclibrary.h b/util/include/util/dynamiclibrary.h index 271e9b7d8..aff37044a 100644 --- a/util/include/util/dynamiclibrary.h +++ b/util/include/util/dynamiclibrary.h @@ -21,7 +21,7 @@ class DynamicLibrary { public: DynamicLibrary(void* handle_); - DynamicLibrary(const std::string& path_); + DynamicLibrary(const std::string& path_, int dlopen_flags_ = RTLD_NOW); ~DynamicLibrary(); diff --git a/util/src/dynamiclibrary.cpp b/util/src/dynamiclibrary.cpp index 4d42c64ef..d5f5559f6 100644 --- a/util/src/dynamiclibrary.cpp +++ b/util/src/dynamiclibrary.cpp @@ -16,7 +16,7 @@ std::string DynamicLibrary::extension() DynamicLibrary::DynamicLibrary(void* handle_) : _handle(handle_){} -DynamicLibrary::DynamicLibrary(const std::string& path_) +DynamicLibrary::DynamicLibrary(const std::string& path_, int dlopen_flags_) { if (path_.empty()) { @@ -37,7 +37,7 @@ DynamicLibrary::DynamicLibrary(const std::string& path_) throw std::runtime_error(ss.str()); } #else - _handle = ::dlopen(path_.c_str(), RTLD_NOW | RTLD_GLOBAL); + _handle = ::dlopen(path_.c_str(), dlopen_flags_); if (!_handle) { const char *dlError = ::dlerror();