From fdc574e1066cb350f5a3e5d4cefc4a92513ada54 Mon Sep 17 00:00:00 2001 From: Evgeniy Patlan Date: Fri, 6 Nov 2026 12:37:08 +0200 Subject: [PATCH] Let the WebAuthn client plugin find the bundled libfido2 The WebAuthn client plugin is installed in lib/mysql/plugins, but its libfido2 dependency is bundled in lib/mysqlsh. Commit c8dc9deee (BUG#40008224) removes the RPATH from every bundled shared object, which also dropped the plugin RPATH kept on purpose by 0a45ed83f. The plugin now names libfido2.so.1 with no way to find it on its own: ldd reports it as not found (or picks a system copy), and dpkg-shlibdeps aborts the Debian package build with "cannot find library libfido2.so.1". It still loads inside mysqlsh only because the executable's RPATH is inherited. Add a SET_RPATH option to install_bundled_binaries() which gives shared objects the same $ORIGIN-relative RPATH to INSTALL_LIBDIR that bundled executables get, and use it for the WebAuthn plugin when libfido2 is bundled. Other plugins and libfido2 itself stay without an RPATH, so the debugedit problem 0a45ed83f avoided does not come back. macOS and Windows are not affected. Extend linux_rpath_test.py to cover the new option. --- cmake/exeutils.cmake | 18 ++++++++++++--- cmake/tests/linux_rpath_test.py | 22 +++++++++++++++++-- src/CMakeLists.txt | 39 ++++++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/cmake/exeutils.cmake b/cmake/exeutils.cmake index 919eeb67e..8dd6cc441 100644 --- a/cmake/exeutils.cmake +++ b/cmake/exeutils.cmake @@ -147,7 +147,7 @@ elseif(NOT WIN32) endfunction() function(get_linux_rpath_command) - set(options) + set(options SET_RPATH) set(oneValueArgs TARGET_BINARY SOURCE_BINARY DESTINATION OUT_COMMAND) set(multiValueArgs) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -166,6 +166,12 @@ elseif(NOT WIN32) set(ACTION SET) endif() + # Shared objects installed outside INSTALL_LIBDIR which depend on bundled + # libraries need an RPATH pointing to INSTALL_LIBDIR. + if(ARG_SET_RPATH) + set(ACTION SET) + endif() + get_binary_rpaths(BINARY "${ARG_SOURCE_BINARY}" OUT_RPATH BINARY_RPATH OUT_RUNPATH BINARY_RUNPATH) # Refresh the generated command if an inspected dependency is rebuilt. set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${ARG_SOURCE_BINARY}") @@ -226,7 +232,8 @@ function(install_bundled_binaries) return() endif() - set(options USE_LIBDIR) + # SET_RPATH: on Linux, set an RPATH to INSTALL_LIBDIR even for shared objects + set(options USE_LIBDIR SET_RPATH) set(oneValueArgs DESCRIPTION DESTINATION TARGET) set(multiValueArgs BINARIES EXTRA_COPY_COMMANDS) cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -281,8 +288,13 @@ function(install_bundled_binaries) if(NOT IS_SYMLINK "${SOURCE_BINARY}") message(STATUS " Bundling binary: ${SOURCE_BINARY}, installation directory: ${ARG_DESTINATION}") if(LINUX) + set(RPATH_OPTIONS) + if(ARG_SET_RPATH) + list(APPEND RPATH_OPTIONS SET_RPATH) + endif() + get_linux_rpath_command(TARGET_BINARY "${COPIED_BINARY}" SOURCE_BINARY "${SOURCE_BINARY}" - DESTINATION "${ARG_DESTINATION}" OUT_COMMAND RPATH_COMMAND) + DESTINATION "${ARG_DESTINATION}" OUT_COMMAND RPATH_COMMAND ${RPATH_OPTIONS}) if(RPATH_COMMAND) message(STATUS " will execute: ${RPATH_COMMAND}") list(APPEND COPY_COMMAND COMMAND ${RPATH_COMMAND}) diff --git a/cmake/tests/linux_rpath_test.py b/cmake/tests/linux_rpath_test.py index 61aa6198b..c7e8bb676 100644 --- a/cmake/tests/linux_rpath_test.py +++ b/cmake/tests/linux_rpath_test.py @@ -86,7 +86,7 @@ def apply(self, **variables): set(INSTALL_LIBDIR "../lib") set(PYTHON_EXECUTABLE "${root}/python-fixture") get_linux_rpath_command(TARGET_BINARY "${copied_binary}" SOURCE_BINARY "${binary}" - DESTINATION "${destination}" OUT_COMMAND command) + DESTINATION "${destination}" OUT_COMMAND command ${options}) if(NOT "${binary}" STREQUAL "${copied_binary}") get_filename_component(copy_dir "${copied_binary}" DIRECTORY) file(COPY "${binary}" DESTINATION "${copy_dir}") @@ -99,7 +99,7 @@ def apply(self, **variables): endif() ''') values = dict(binary=str(self.binary), copied_binary=str(self.binary), - destination=".", root=str(self.root), + destination=".", root=str(self.root), options="", exeutils=str(OPTIONS.exeutils), CMAKE_READELF=OPTIONS.readelf, PATCHELF_EXECUTABLE=OPTIONS.patchelf) @@ -191,6 +191,14 @@ def test_set_over_short_and_long_paths(self): self.assertEqual([("RPATH", "$ORIGIN/../lib")], self.tags()) run(str(self.binary)) + def test_set_rpath_for_shared_object(self): + # e.g. a plugin linked to a library in the build tree, which is bundled + self.prepare_path("/original/build/library") + copied = self.root / "plugins" / self.binary.name + self.assert_success(self.apply(copied_binary=str(copied), + destination="plugins", options="SET_RPATH")) + self.assertEqual([("RPATH", "$ORIGIN/../../lib")], self.tags(copied)) + def test_equal_runpath_is_converted(self): self.use_executable() self.prepare_path("$ORIGIN/../lib") @@ -303,6 +311,8 @@ def test_copy_policy_with_both_generators(self): run(OPTIONS.cc, "-shared", "-fPIC", str(self.root / "main.c"), "-o", str(lib)) run(OPTIONS.patchelf, "--set-rpath", "/original/library", str(lib)) (project / "libfixture.so").symlink_to(lib.name) + for name in ("plugin.so", "plugin_with_rpath.so"): + shutil.copy2(lib, project / name) shutil.copy2(self.binary, project / "helper") shutil.copy2(self.binary, project / "python-fixture") # Server build-tree helpers can contain only CMake's RPATH padding. @@ -325,6 +335,10 @@ def test_copy_policy_with_both_generators(self): DESTINATION libexec/mysqlsh TARGET bundle) install_bundled_binaries(DESCRIPTION python BINARIES "${PYTHON_EXECUTABLE}" DESTINATION lib/mysqlsh/bin TARGET bundle) +install_bundled_binaries(DESCRIPTION plugin BINARIES "${CMAKE_SOURCE_DIR}/plugin.so" + DESTINATION lib/mysql/plugins TARGET bundle) +install_bundled_binaries(DESCRIPTION plugin BINARIES "${CMAKE_SOURCE_DIR}/plugin_with_rpath.so" + DESTINATION lib/mysql/plugins TARGET bundle SET_RPATH) ''') for generator in ("Ninja", "Unix Makefiles"): for bundled_ssl in ("ON", "OFF"): @@ -345,6 +359,10 @@ def test_copy_policy_with_both_generators(self): if bundled_ssl == "ON" else []) self.assertEqual(expected, self.tags(helper)) + plugins = build / "lib/mysql/plugins" + self.assertEqual([], self.tags(plugins / "plugin.so")) + self.assertEqual([("RPATH", "$ORIGIN/../../mysqlsh")], + self.tags(plugins / "plugin_with_rpath.so")) before = snapshot(helper) run(OPTIONS.cmake, "--build", str(build)) self.assertEqual(before, snapshot(helper)) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9dba8bc18..9bea4cd54 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -439,12 +439,39 @@ IF(AUTH_CLIENT_PLUGINS) ) ENDIF() - install_bundled_binaries( - DESCRIPTION "MySQL authentication plugins" - BINARIES ${AUTH_CLIENT_PLUGINS} - DESTINATION "${INSTALL_MYSQL_PLUGINS_DIR}" - TARGET mysqlsh - ) + SET(AUTH_CLIENT_PLUGINS_WITH_RPATH) + SET(AUTH_CLIENT_PLUGINS_WITHOUT_RPATH) + + # The WebAuthn plugin is installed outside INSTALL_LIBDIR, but it needs the + # bundled libfido2 from there, so it gets an RPATH. The other plugins get + # none. With BUNDLED_OPENSSL, those linked to OpenSSL rely on the RPATH of + # the mysqlsh executable that loads them to find the bundled copy. + FOREACH(_plugin ${AUTH_CLIENT_PLUGINS}) + IF(AUTH_CLIENT_PLUGINS_DEPS AND _plugin MATCHES "authentication_webauthn_client\\.") + LIST(APPEND AUTH_CLIENT_PLUGINS_WITH_RPATH "${_plugin}") + ELSE() + LIST(APPEND AUTH_CLIENT_PLUGINS_WITHOUT_RPATH "${_plugin}") + ENDIF() + ENDFOREACH() + + IF(AUTH_CLIENT_PLUGINS_WITHOUT_RPATH) + install_bundled_binaries( + DESCRIPTION "MySQL authentication plugins" + BINARIES ${AUTH_CLIENT_PLUGINS_WITHOUT_RPATH} + DESTINATION "${INSTALL_MYSQL_PLUGINS_DIR}" + TARGET mysqlsh + ) + ENDIF() + + IF(AUTH_CLIENT_PLUGINS_WITH_RPATH) + install_bundled_binaries( + DESCRIPTION "MySQL authentication plugins with bundled dependencies" + BINARIES ${AUTH_CLIENT_PLUGINS_WITH_RPATH} + DESTINATION "${INSTALL_MYSQL_PLUGINS_DIR}" + TARGET mysqlsh + SET_RPATH + ) + ENDIF() ELSE() MESSAGE(WARNING "Could not find Authentication Client Plugins!") ENDIF()