diff --git a/cmake/PjPluginManifest.cmake b/cmake/PjPluginManifest.cmake index d76e783..a92bf94 100644 --- a/cmake/PjPluginManifest.cmake +++ b/cmake/PjPluginManifest.cmake @@ -32,6 +32,37 @@ function(pj_emit_plugin_manifest TARGET) message(FATAL_ERROR "pj_emit_plugin_manifest(${TARGET}): FAMILY is required") endif() + # Symbol isolation — functional replacement for RTLD_DEEPBIND. + # + # Two complementary mechanisms are needed: + # + # 1. -fvisibility=hidden (compile-time): hides symbols DEFINED in the plugin's + # own source files. Prevents them from being interposable by the host. + # + # 2. -Wl,-Bsymbolic-functions (link-time): makes function calls WITHIN the .so + # resolve to the definitions inside it, bypassing the PLT entirely. + # This is critical for statically bundled deps (e.g. libssl.a from Conan) + # that were compiled WITHOUT -fvisibility=hidden: their symbols enter the .so + # with DEFAULT visibility, and without -Bsymbolic-functions their calls would + # still go through PLT → resolved to the host's namespace first → crash. + # + # Together: all function calls inside the plugin use the embedded static copies. + # The two boot-level exports (pj_plugin_abi_version + PJ_get__vtable) + # keep visibility("default") via the PJ_*_PLUGIN macros and are unaffected. + # malloc / pthread / system calls are NOT defined in the plugin, so they still + # resolve to the host — ASAN malloc interposition works correctly. + # + # -Bsymbolic-functions is Linux/ELF-specific. On macOS the linker uses + # two-level namespace by default (equivalent behavior), so the flag is omitted. + set_target_properties(${TARGET} PROPERTIES + CXX_VISIBILITY_PRESET hidden + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + ) + target_link_options(${TARGET} PRIVATE + $<$:-Wl,-Bsymbolic-functions> + ) + set(_valid_families data_source message_parser toolbox dialog) list(FIND _valid_families "${ARG_FAMILY}" _family_idx) if(_family_idx LESS 0) diff --git a/pj_media/demos/CMakeLists.txt b/pj_media/demos/CMakeLists.txt index 2464ea4..2f8d225 100644 --- a/pj_media/demos/CMakeLists.txt +++ b/pj_media/demos/CMakeLists.txt @@ -60,14 +60,4 @@ if(TARGET pj_media_qt) target_compile_options(mp4_video_viewer PRIVATE ${PJ_WARNING_FLAGS}) target_link_libraries(mp4_video_viewer PRIVATE pj_media_qt pj_media_core) endif() -elseif(PJ_BUILD_DIALOG_ENGINE_QT) - find_package(Qt6 REQUIRED COMPONENTS Widgets) - set(CMAKE_AUTOMOC ON) - add_executable(mcap_image_viewer mcap_image_viewer.cpp image_widget.hpp) - target_compile_features(mcap_image_viewer PRIVATE cxx_std_20) - target_compile_options(mcap_image_viewer PRIVATE ${PJ_WARNING_FLAGS}) - target_link_libraries(mcap_image_viewer PRIVATE - pj_media_core pj_datastore mcap::mcap libjpeg-turbo::libjpeg-turbo - Qt6::Widgets - ) endif() diff --git a/pj_plugins/src/detail/library_loader.hpp b/pj_plugins/src/detail/library_loader.hpp index d5bf9f4..53bc5a8 100644 --- a/pj_plugins/src/detail/library_loader.hpp +++ b/pj_plugins/src/detail/library_loader.hpp @@ -38,9 +38,15 @@ inline Expected loadLibraryHandle(std::string_view path) { // breaks LD_PRELOAD'd malloc interposition, which makes every plugin // dlopen fail under AddressSanitizer (and similarly for jemalloc / // tcmalloc interposition in production). Plugin-local symbol isolation - // is instead achieved by building plugins with -fvisibility=hidden and - // explicitly marking only the boot-level exports - // (pj_plugin_abi_version + PJ_get__vtable) as default visible. + // uses two build-time mechanisms (cmake/PjPluginManifest.cmake): + // 1. -fvisibility=hidden: hides symbols defined in plugin source files. + // 2. -Wl,-Bsymbolic-functions (Linux): function calls within the .so + // resolve to the embedded static copies, bypassing PLT. This covers + // deps compiled without -fvisibility=hidden (e.g. libssl.a from Conan) + // whose symbols enter the .so with default visibility and whose calls + // would otherwise resolve to the host's namespace first via PLT. + // malloc/pthread/system calls are NOT defined in the plugin so they still + // reach the host — ASAN malloc interposition works correctly. int flags = RTLD_NOW | RTLD_LOCAL; void* handle = dlopen(std::string(path).c_str(), flags); if (handle == nullptr) {