【Vol.3 変更履歴の追跡】CentOS7 で glibc をアップデート後、 mod_php + httpd の logrotate (reload ) が失敗する: /lib64/libresolv.so.2: symbol __h_errno, version GLIBC_PRIVATE not defined in file libc.so.6 with link time reference

hiboma.hatenadiary.jp

hiboma.hatenadiary.jp

上記エントリの続きです

前回まで調べていたこと

  • CentOS7 で glibc をアップデート後、 mod_php + httpd の logrotate (reload ) が失敗するのを調べていた
  • 失敗する際に以下のログを出す

Graceful restart requested, doing restart httpd: Syntax error on line 39 of /etc/httpd/conf/httpd.conf: Syntax error on line 1 of /etc/httpd/conf.modules.d/php7.load: /lib64/libresolv.so.2: symbol __h_errno, version GLIBC_PRIVATE not defined in file libc.so.6 with link time reference

  • 前回のエントリまでで再現を取ることができた

本エントリのサマリ

前回のまとめに「glibc をアップデートすると再現する」とさらっと書いているが、「glibc をどのバージョンから、どのバージョンにアップデートすると再現するの?」 をさらに調べて本エントリにまとめている。さらに __h_errno の変更がどういった経緯で入ったのかも調べた

  • 問題となる __h_errno シンボルは、 CentOS 7.5.1804 でリリースされた glibc-2.17-222.el7.x86_64.rpm から登場する
  • それ以前は h_errno というシンボルだっただった
  • h_errno --> __h_errno への変更は、 glibc upstream が POSIX 2008 の変更に追随するためのパッチを取り込んだことによる

というサマリです

1. 調査: RPM をダウンロード, 展開, シンボルを調べる

http://mirror.centos.orghttp://vault.centos.org から RPM をダウンロードして展開、libc.so.6 のシンボルを地道に見ていく作業で調べた

cd /tmp
mkdir glibc-2.17-196
cd    glibc-2.17-196
wget http://vault.centos.org/7.4.1708/os/x86_64/Packages/glibc-2.17-196.el7.x86_64.rpm
rpm2cpio glibc-2.17-196.el7.x86_64.rpm  | cpio -idv
nm ./lib64/libc.so.6 | grep h_errno$

CentOS7.4.1708: glibc-2.17-196.el7.x86_64.rpm

$ nm ./lib64/libc.so.6 | grep h_errno$
000000000000007c b __libc_h_errno
000000000000007c B h_errno

http://vault.centos.org/7.4.1708/os/x86_64/Packages/glibc-2.17-196.el7.x86_64.rpm

CentOS7.4.1708: glibc-2.17-196.el7_4.2.x86_64.rpm

$ nm ./lib64/libc.so.6 | grep h_errno$
000000000000007c b __libc_h_errno
000000000000007c B h_errno

http://vault.centos.org/7.4.1708/updates/x86_64/Packages/glibc-2.17-196.el7_4.2.x86_64.rpm

CentOS7.5.1804: glibc-2.17-222.el7.x86_64.rpm

$ nm ./lib64/libc.so.6 | grep h_errno$
000000000000008c B __h_errno 👈
000000000000008c b __libc_h_errno

これだー!!!!!

http://vault.centos.org/7.5.1804/os/x86_64/Packages/glibc-2.17-222.el7.x86_64.rpm

表にまとめてみましょう

リリース バージョン __h_errno の有無
7.4.1708 glibc-2.17-196.el7.x86_64.rpm x
7.4.1708 glibc-2.17-196.el7_4.2.x86_64.rpm x
7.5.1804 glibc-2.17-222.el7.x86_64.rpm 👈 o
7.6.1810 glibc-2.17-260.el7.x86_64 o
7.6.1810 glibc-2.17-260.el7_6.3.x86_64 o

👈 をつけた CentOS7.5.1804 glibc-2.17-222.el7.x86_64.rpm から __h_errno が登場する

__h_errno の有無 が x から o の バージョンにアップデートすると、このエントリで追いかけている問題を踏んでしまうと絞り込める (実際には全部のケースで試していないので推論です)

2. 深追い: __h_errno に変更したパッチを探す

CentOS 7.5.1804 glibc-2.17-222.el7 のどういったパッチが入ったことで h_errno__h_errno に変更されたのかを調べていく

SRPM を展開して探す

古い SRPM は vault.centos.org から探し出します

$ wget https://vault.centos.org/7.5.1804/os/Source/SPackages/glibc-2.17-222.el7.src.rpm
$ rpm -ivh glibc-2.17-222.el7.src.rpm

で、適当に grep して以下を見つけました

~/rpmbuild/SOURCES/glibc-rh677316-h_errno.patch

パッチの中身は下記の通りです

Partial backport of this upstream commit:

commit 9acacaa02f3b75fddc07a56f3d848df45281a5de
Author: Joseph Myers <joseph@codesourcery.com>
Date:   Fri Jun 12 10:10:18 2015 +0000

    Fix h_errno namespace (bug 18520).

The linknamespace test changes in conform/Makefile are not included here
because glibc 2.17 did not have these tests.

diff --git a/include/netdb.h b/include/netdb.h
index b6d7b90bbf8abd2e..6a6dca9ef57aaa37 100644
--- a/include/netdb.h
+++ b/include/netdb.h
@@ -8,7 +8,7 @@
 #  if IS_IN (libc)
 #   define h_errno __libc_h_errno
 #  else
-#   define h_errno h_errno    /* For #ifndef h_errno tests.  */
+#   define h_errno __h_errno
 #  endif
 extern __thread int h_errno attribute_tls_model_ie;
 # endif /* IS_IN_LIB */
diff --git a/inet/herrno.c b/inet/herrno.c
index 1802d0e00563839a..0cd84445190728b3 100644
--- a/inet/herrno.c
+++ b/inet/herrno.c
@@ -24,7 +24,7 @@
 /* We need to have the error status variable of the resolver
    accessible in the libc.  */
 
-__thread int h_errno;
-extern __thread int __libc_h_errno __attribute__ ((alias ("h_errno")))
+__thread int __h_errno;
+extern __thread int __libc_h_errno __attribute__ ((alias ("__h_errno")))
   attribute_hidden;
 #define h_errno __libc_h_errno
diff --git a/nptl/herrno.c b/nptl/herrno.c
index c0488e4f6754873f..5056e3df88211123 100644
--- a/nptl/herrno.c
+++ b/nptl/herrno.c
@@ -23,12 +23,12 @@
 
 /* We need to have the error status variable of the resolver
    accessible in the libc.  */
-extern __thread int h_errno;
+extern __thread int __h_errno;
 
 
 /* When threaded, h_errno may be a per-thread variable.  */
 int *
 __h_errno_location (void)
 {
-  return &h_errno;
+  return &__h_errno;
 }
diff --git a/resolv/Versions b/resolv/Versions
index 93faf1e2f5faac79..152ef3f68f9a8b48 100644
--- a/resolv/Versions
+++ b/resolv/Versions
@@ -26,7 +26,7 @@ libc {
   GLIBC_PRIVATE {
     __gai_sigqueue;
 
-    h_errno; __resp;
+    __h_errno; __resp;
 
     __res_maybe_init; __res_iclose;
   }

コミットログが短い!!!1

3. オリジナナルのコミット/チケット/issue を探す

SRPM に含まれる libc-rh677316-h_errno.patch は、コミットログの詳細を削っているため変更の理由や文脈がよくわからない. 地道にググって探し出します

Fix h_errno namespace (bug 18520). を手がかりに調べいくと下記がヒットする

上記からコミットログを転載する

 The 2008 edition of POSIX removed h_errno, but some functions still
bring in references to the h_errno external symbol.  As this symbol is
not a part of the public ABI (only __h_errno_location is), this patch
fixes this by renaming the GLIBC_PRIVATE TLS symbol to __h_errno.

Tested for x86_64 and x86 (testsuite, and comparison of installed
shared libraries).  Disassembly of all shared libraries using h_errno
changes because of the renaming (and changes to associated TLS / GOT
offsets in some cases); disassembly of libpthread on x86_64 changes
more substantially because the enlargement of .dynsym affects
subsequent addresses.

2015-06-11  Joseph Myers  

    [BZ #18520]
    * inet/herrno.c (h_errno): Rename to __h_errno.
    (__libc_h_errno): Define as alias of __h_errno not h_errno.
    * include/netdb.h [IS_IN_LIB && !IS_IN (libc)] (h_errno): Define
    to __h_errno instead of h_errno.
    * nptl/herrno.c (h_errno): Rename to __h_errno.
    (__h_errno_location): Refer to __h_errno not h_errno.
    * resolv/Versions (h_errno): Rename to __h_errno.
    * conform/Makefile (test-xfail-XOPEN2K8/grp.h/linknamespace):
    Remove variable.
    (test-xfail-XOPEN2K8/pwd.h/linknamespace): Likewise.

なるほど? Google 翻訳を載せておきます

POSIXの2008年版はh_errnoを削除しました、しかし、いくつかの関数はまだh_errno外部シンボルへの参照を持ち込みます。このシンボルはパブリックABIの一部ではないため(h_errno_locationのみ)、GLIBC_PRIVATE TLSシンボルの名前をh_errnoに変更することでこれを修正します。

x86_64とx86用にテスト済み(testsuite、およびインストール済み共有ライブラリの比較)。 h_errnoを使用したすべての共有ライブラリの逆アセンブリは、名前の変更(および場合によっては関連するTLS / GOTオフセットの変更)によって変更されます。 x86_64でのlibpthreadの逆アセンブリは、.dynsymの拡大が後続のアドレスに影響を与えるため、より大幅に変更されます。

glibc upstream が POSIX 2008 に追随するための変更だったのだと理解した

GLIBC_PRIVATE TLS とは?

GLIBC_PRIVATE についてはここを読んだ

https://groups.google.com/forum/#!topic/uk.comp.os.linux/PX3rstGtZRQ

GLIBC_PRIVATE is the version given to symbols exported by the GNU C library only for the purpose of communicating with other parts of the library.

関数のスコープを glibc 内でのみに閉じる用途で使うものぽい. あと TLSThread Locl Storage かなぁ.

readelf で確認する

$ readelf -a ./lib64/libc.so.6  | grep h_errno
  1483: 000000000000007c     4 TLS     GLOBAL DEFAULT   23 h_errno@@GLIBC_PRIVATE
  1928: 0000000000110390    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location@@GLIBC_2.2.5
  3875: 000000000000007c     4 TLS     LOCAL  DEFAULT   23 __libc_h_errno
  5135: 0000000000110390    17 FUNC    LOCAL  DEFAULT   12 __GI___h_errno_location
  6483: 0000000000110390    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location
  7573: 000000000000007c     4 TLS     GLOBAL DEFAULT   23 h_errno
$ readelf -a ./lib64/libc.so.6  | grep h_errno
  1495: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 h_errno@GLIBC_PRIVATE
  1859: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 __h_errno@@GLIBC_PRIVATE
  1946: 0000000000118cf0    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location@@GLIBC_2.2.5
  3925: 000000000000008c     4 TLS     LOCAL  DEFAULT   23 __libc_h_errno
  5213: 0000000000118cf0    17 FUNC    LOCAL  DEFAULT   12 __GI___h_errno_location
  6580: 0000000000118cf0    17 FUNC    GLOBAL DEFAULT   12 __h_errno_location
  7075: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 __h_errno
  7147: 000000000000008c     4 TLS     GLOBAL DEFAULT   23 h_errno@GLIBC_PRIVATE 👈

4. POSIX 2008 とは何なのか?

さて、先のコミットログで 2008 edition of POSIX についての記述があったのでさらに調べる

まず見つけたのは gethostbyname(3) の man ページである

POSIX.1-2008 では gethostbyname(), gethostbyaddr(), h_errno の仕様が削除されている。 代わりに、 getaddrinfo(3) と getnameinfo(3) の使用が推奨されている。

getaddrinfo() 関数は、 gethostbyname(3) と getservbyname(3) の機能をまとめて一つのインターフェースにしたものであるが、 これらの関数と違い、 getaddrinfo() はリエントラントであり、 getaddrinfo() を使うことでプログラムは IPv4IPv6 の違いに関する依存関係を なくすことができる。

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/gethostbyname.3.html

さらにググっていくと POSIX のページと思われる http://pubs.opengroup.org/onlinepubs/9699919799.2008edition/ を見つけた

h_errno

Applications are recommended not to use this error return code. Previously it was set by the gethostbyaddr() and gethostbyname() functions.

POSIX で変更が入った理由が良く分からないが、IPv4/IPv6 の話周りを整えるためなんだろうか?

まとめ

  • glibc のパッチまで踏み込んで追いかけた
  • glibc のメンテナさんはは 上位のアプリケーションで問題が出ないように実装を進めてくれているのだろうが、今回のように不具合として踏んでしまうケースもあるのだろうなぁ
  • glibcPOSIX の変更なんぞを普段全く気にすることなく生きている

参考