libmysqlclient.so.18はいかにしてlibz.so.1を壊したか

  • 投稿日:
  • by
  • カテゴリ:

結論

同じシンボルをGLOBALで公開している複数の共有ライブラリをdlopenを使って動的にロードした場合に、解決されるシンボルがファイル間で前後することがある。

ということが原因のようです。

サンプルコード

https://github.com/usualoma/break-zlibにサンプルコードがあります。

発生した問題

以下のようなDockerfileで環境を構築したときに

「(perlでImage::Magickを使って)PNG画像のサムネイルが生成できない」という問題が発生し、条件を絞り込んでいくとどうも「DBD::mysqlをImage::Magickよりも先に読み込んだ場合に生成できなくなる」ということが分かりました。ポイントは、

  • MySQL Yum Repositoryのmysql-community-libs-compatでlibmysqlclient.so.18をインストールしている
  • libmysqlclient.so.18にリンクされた共有ライブラリを動的な環境で読み込んでいる

というころにあるようです。

この環境では、以下のようにDBD::mysqlを先に読み込んだ場合には失敗し、後に読み込んだ場合には成功します。

さらに(識者からの助言をもらいつつ)もう少し追っていくと、PNG画像の読み込み時のgzipの初期化に失敗していることが分かりました。

以下のように、zlibを直接呼び出すperlモジュールでも問題が発生します。

発生条件

色々試した結果、https://github.com/usualoma/break-zlib/tree/master/srcにあるようなコードで、

  • libmysqlclient.so.18にリンクした共有ライブラリを作成
  • libz.so.1にリンクした共有ライブラリを作成
  • それらを、libmysqlclient ->libz の順に動的にロードする

というやり方で発生させることができました。

以下のようにLD_PRELOAD使った場合には発生しなかったので「ロードの順序」だけではなく、「動的にロードすること」が条件になりそうです。

発生した理由

$ ldd /usr/lib64/mysql/libmysqlclient.so.18

の結果にlibz.so.1が含まれてもいるのですが、

$ readelf -Ws /usr/lib64/mysql/libmysqlclient.so.18.1.0

の結果にも「inflateReset」のようなlibzのシンボルがGLOBALとして含まれており、結果としてlibzの共有ライブラリとして利用できるようになっているようです。

gdbでブレークポイントを設定して見ていったところ、

  • inflateReset2は、libz.so.1のもの
  • その中で呼ばれるinflateResetは、libmysqlclient.so.18のもの

というようになっていて、それぞれに含まれるzlibのバージョンの差異によりエラーが発生しているようでした。

mysql.specを見る限り、WITH_EMBEDDED_SHARED_LIBRARYは明示的に1を指定しているようなので、libzを含むところまでは意図してやっていそうでしたが、GLOBALになっている理由はよく分かりませんでした。(mysql-community-libsからインストールされる/usr/lib64/mysql/libmysqlclient.so.20.3.15には、libzが含まれているものGLOBALではありませんでした。)

感想

mysql-community-libs-compatをインストールすると依存関係でmysql-community-libsが入り、/usr/lib64/mysql/libmysqlclient.soがlibmysqlclient.so.20を指すようになるので、MySQL Yum Repositoryとしてはlibmysqlclient.so.20を推奨っぽいので、可能な場合にはそちらにリンクしたモジュールをビルドし直した方がいいのかもしれません。

ビルドをしたくない場合、CentOSのパッケージで済まそうとすると基本的にはlibmysqlclient.so.18にリンクされていて上の状況は割と発生しそうな気がするので、その場合にはlibz.so.1を先にロードしてあげたりするなどの対応が必要かもしれません。