Hide statically linked libstdc++ symbols from the extension - #254
Open
sam-saffron-jarvis wants to merge 1 commit into
Open
sam-saffron-jarvis wants to merge 1 commit into
sam-saffron-jarvis wants to merge 1 commit into
Conversation
Precompiled gems are built with CB_STATIC_STDLIB, which links libstdc++ and libgcc statically into libcouchbase.so, but every symbol from those archives is still exported (8423 std:: symbols, 745 STB_GNU_UNIQUE). Ruby loads extensions with RTLD_GLOBAL, so this private GCC 11 runtime interposes on the system libstdc++ used by other C++ extensions and crashes them, e.g. mini_racer (rubyjs/mini_racer#422). Link with --exclude-libs,ALL on ELF platforms so archive symbols stay local to the extension.
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The precompiled Linux gems are built with
CB_STATIC_STDLIB=1, so libstdc++ and libgcc are linked statically intolibcouchbase.so. Nothing hides those symbols, so the extension re-exports a full libstdc++ (built with GCC 11.4). In a source build of 3.8.2 with the release flags,libcouchbase.soexports 8,423std::symbols, 745 of themSTB_GNU_UNIQUE, including the locale facet ids such asstd::num_put<char>::id.Ruby loads extensions with
RTLD_GLOBAL, and glibc resolvesSTB_GNU_UNIQUEsymbols process-wide to the first definition. As a result, when another C++ extension loads after couchbase, the systemlibstdc++.so.6binds parts of itself to couchbase's private copy.LD_DEBUG=bindingsshows this:The other library ends up with facet ids from one libstdc++ and the facet table from another. The first
ostream << longthen dereferences a missing facet and segfaults at0x20. With the load order reversed, couchbase itself aborts withstd::bad_cast. This only shows when the system libstdc++ differs enough from couchbase's GCC 11 copy, e.g. Debian trixie (GCC 14), Arch (GCC 16) and theruby:3.4-slimimages. Bookworm happens to work.Reported downstream in rubyjs/mini_racer#422 (minimal repro: rubyjs/mini_racer#422 (comment)):
mini_racer isn't the only extension affected. A three-line C++ extension that does
std::ostringstream os; os << 42L;crashes the same way when it is loaded after couchbase withRTLD_LOCAL|RTLD_DEEPBIND. The leaked runtime affects any C++ extension that isolates itself. Current workaround for users:LD_PRELOAD=libstdc++.so.6.Fix
On ELF platforms, link the extension with
--exclude-libs,ALL. Symbols that come from static archives (libstdc++, libgcc, BoringSSL, ...) stay local tolibcouchbase.so.Init_libcouchbaseand the extension's own code are unaffected. Apple and Windows/MinGW are left unchanged.Verification
x86_64 Arch Linux (libstdc++ 6.0.36 / GCC 16), Ruby 3.4.10. couchbase 3.8.2 was built from the source gem with
CB_STATIC_STDLIB=1 CB_STATIC_BORINGSSL=1(as inbin/jenkins/build-gem.sh), before and after this change:std::symbolsrequire "couchbase"; require "mini_racer"; evalstd::bad_cast)RTLD_DEEPBIND) after couchbaseCluster.connectattempt (no server: expected timeout error) + V8 in the same processNot tested: operations against a live server, aarch64, Alpine/musl.
Related: rubyjs/mini_racer#422. mini_racer is considering a matching defensive change (linking its own libstdc++ privately), but the leak itself originates here.
Note: this PR was prepared by an AI assistant (Jarvis) on behalf of Sam Saffron. The fix is a single link flag, so feel free to apply it directly if handling the CLA/Gerrit flow for this PR is inconvenient.