Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions cmake/PjPluginManifest.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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_<family>_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
$<$<PLATFORM_ID:Linux>:-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)
Expand Down
10 changes: 0 additions & 10 deletions pj_media/demos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
12 changes: 9 additions & 3 deletions pj_plugins/src/detail/library_loader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,15 @@ inline Expected<void*> 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_<family>_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) {
Expand Down
Loading