Skip to content

fix(plugins): enforce -fvisibility=hidden on all plugin .so targets#73

Merged
pabloinigoblasco merged 4 commits intodevelopmentfrom
fix/plugin-visibility-hidden
Apr 27, 2026
Merged

fix(plugins): enforce -fvisibility=hidden on all plugin .so targets#73
pabloinigoblasco merged 4 commits intodevelopmentfrom
fix/plugin-visibility-hidden

Conversation

@pabloinigoblasco
Copy link
Copy Markdown
Collaborator

Summary

  • pj_emit_plugin_manifest() now sets CXX_VISIBILITY_PRESET=hidden, C_VISIBILITY_PRESET=hidden, and VISIBILITY_INLINES_HIDDEN=ON on every plugin .so target
  • The two boot-level exports (pj_plugin_abi_version, PJ_get_<family>_vtable) remain default-visible via the PJ_*_PLUGIN macros — no change needed there
  • Statically bundled deps (e.g. OpenSSL inside paho-mqtt-c) are now hidden and cannot conflict with the host's shared libraries
  • Updates the library_loader.hpp comment to reflect that the mechanism is now actually enforced

Why

With RTLD_DEEPBIND removed in v4 ABI, the intended replacement for symbol isolation was -fvisibility=hidden on plugin targets. This was documented in comments but never implemented in the build system, leaving plugin .so files with fully default-visible symbol tables. Any statically linked dependency whose symbols clashed with the host's shared libraries could crash at runtime.

Test plan

  • Build all plugins — verify no link errors
  • Check that nm -D <plugin>.so | grep SSL shows no exported OpenSSL symbols
  • MQTT SSL connection test passes

🤖 Generated with Claude Code

The elseif(PJ_BUILD_DIALOG_ENGINE_QT) branch built a basic Qt::Widgets
mcap_image_viewer as a fallback when pj_media_qt was not available. Now
that pj_media_qt exists and is the correct path for media demos, this
fallback is dead code: it never executes when pj_media_qt is present,
and building a raw-widget demo without QRhi support provides no value
when pj_media_qt is absent. Remove it to keep the demo CMakeLists clean.
pj_emit_plugin_manifest now sets CXX/C_VISIBILITY_PRESET=hidden and
VISIBILITY_INLINES_HIDDEN=ON on every plugin target. This completes
the RTLD_DEEPBIND replacement strategy: statically bundled deps (e.g.
OpenSSL inside paho-mqtt-c) cannot conflict with the host's shared
libs because their symbols are hidden and RTLD_LOCAL keeps them out of
the global namespace. Only pj_plugin_abi_version and
PJ_get_<family>_vtable remain default-visible via the PJ_*_PLUGIN
macros.

Update the library_loader.hpp comment to reflect that the mechanism is
now actually in place, not merely aspirational.
-fvisibility=hidden alone is insufficient when static deps (e.g.
libssl.a from Conan) are compiled without that flag: their symbols
enter the plugin .so with DEFAULT visibility and their calls go through
PLT — which resolves to the host's namespace first (system libssl),
causing version-mismatch crashes.

-Wl,-Bsymbolic-functions resolves all function calls within the .so
directly to the embedded definition, bypassing PLT entirely.
This covers pre-compiled static libs like OpenSSL regardless of how
they were built. Only applied on Linux/ELF (macOS uses two-level
namespace by default — equivalent behavior).

Empirically verified with objdump: with -Bsymbolic-functions, calls
from plugin_func to SSL_new are 'call SSL_new' (direct, no @plt).
Without it, they remain 'call SSL_new@plt' even with -fvisibility=hidden.

malloc/pthread/system calls are not defined in the plugin so they still
resolve to the host — ASAN malloc interposition is unaffected.
@pabloinigoblasco pabloinigoblasco merged commit dbb1984 into development Apr 27, 2026
2 checks passed
@pabloinigoblasco pabloinigoblasco deleted the fix/plugin-visibility-hidden branch May 4, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant