「名前解決 (name resolution) 」というと、最近ではIPアドレスとホスト名を関連付けるDNS (Domain Name System)を連想するものの、ユーザ名やグループ名をuid/gidとマッピングしたり、/etc/hostsのレベルでIPアドレスを解決するなど、1台のホストの中でも名前解決が必要な場面は多々あります。
この機能はNSS (Name Service Switch)と呼ばれ、glibcが提供するシステムの基本機能の一部になっています。
最近、glibc-2.32に更新した際、このNSSがらみで面白いトラブルに遭遇し、glibcの知られざる機能(?)を発見したので、今回はその事例を紹介してみます。
発端
時期を逸して本連載では紹介していなかったものの、Plamo-7.1のリリース後約1年を経過し、その間に更新されたパッケージもたまってきたので、2020年5月にそれらを整理したPlamo-7.2 をリリースしました。
Plamo-7.2の時点ではglibcは2.30 を採用していたものの、半年ごとに新バージョンがリリースされるglibcのこと、ふと気づくと2.32 がリリースされています。
そろそろバージョンアップしておこうか、と考えて、2.30用のビルドスクリプトのオプション指定等はそのまま流用し、2.32をパッケージ化しました。
Plamo Linuxでは、glibcのソースコードからglibc (glibc-2.32-x86_64-B1.txz)とlibc (libc-2.32-x86_64-B1.txz)の2つのパッケージを作ります。
configureやビルドの過程を眺めていても特にエラーはありませんし、できたパッケージを確認しても必要なファイルは揃っているようです。そのため、特に気にすることもなくupdatepkgで更新しようとしたところ、libcで「パッケージが壊れている 」旨のエラーが発生しました。
$ sudo updatepkg glibc-2.32-x86_64-B1.txz
[sudo] kojima のパスワード:
removing glibc-2.30
Removing package glibc...
Removing files:
--&lgt; Deleting symlink lib/ld-linux-x86-64.so.2
...
glibc-2.32-x86_64-B1 のインストールスクリプトを実行中
processing [ glibc ] ...
glibc package initializing
Replace /etc/ld.so.conf, and backup it to /etc/ld.so.conf.old
'/etc/ld.so.conf' -&lgt; '/etc/ld.so.conf.old'
glibc timezone configuring
glibc initialize finished
$ sudo updatepkg libc-2.32-x86_64-B1.txz
removing libc-2.30
Removing package libc...
...
--&lgt; Deleting empty directory usr/include/bits
--&lgt; Deleting empty directory usr/include/arpa
Segmentation fault
expr: syntax error
libc-2.32-x86_64-B2 のインストール中
PACKAGE DESCRIPTION:
libc-2.32-x86_64-B1.txzをインストールできませんでした。
パッケージが壊れているようです(tar のエラーコード: 139)
「あれれ、libcパッケージを作り損ねたのかな?」と思って、中身を確認しても問題はありません。
$ tar tvf libc-2.32-x86_64-B1.txz
drwxr-xr-x root/root 0 2020-08-16 08:37:03 usr/
drwxr-xr-x root/root 0 2020-08-16 08:36:41 usr/include/
...
-rw-r--r-- root/root 1503 2020-08-16 09:00:00 install/doinst.sh
$ mkdir Work ; cd Work
$ tar xvf ../libc-2.32-x86_64-B1.txz
usr/
usr/include/
usr/include/limits.h
....
$ ls -l usr/lib
Mcrt1.o libBrokenLocale.so libdl.so libnss_db.so librt.so
Scrt1.o libanl.a libg.a libnss_dns.so libthread_db.so
crt1.o libanl.so libm-2.32.a libnss_files.so libutil.a
....
「パッケージは別に壊れてないようだけどなぁ?」と首を傾( かし ) げながら、再度installpkgを試してみてもやはり同じエラーになります。
これはどうやらパッケージではなく管理ツール側の問題らしい、と気づいて、パッケージ管理用スクリプトを追いかけてみると、新しいパッケージをインストールする/sbin/installpkg2コマンドの377行目、展開前のファイルサイズを"ls -l "で調べているところで落ちているようでした。
376
377 COMPRESSED=`ls -l $package | crunch | cut -f5 -d' '`
378 if [ "$compress" = "bzip2" ]; then
379 UNCOMPRESSED=`bzcat $package | wc -c`
SegFaultの内容をdmesgで確認すると、確かに"ls"が原因のようなものの、落ちた場所はlibc-2.32.so の中と言っています。
$ dmesg
....
[ 1291.190826] ls[3525]: segfault at e5 ip 00007f0d689a4f90
sp 00007ffcb9978530 error 4 in libc-2.32.so[7f0d6888f000+167000]
[ 1291.191697] Code: ff 48 85 c0 0f 84 9d 00 00 00 41 80 3c 24 ff 0f
85 b8 00 00 00 48 8b 05 56 0e 0a 00 49 0f be 16 4c 89 f5 64 48 8b
08 48 89 d0 <f6&lgt; 44 51 01 20 74 1c 66 0f 1f 84 00 00 00 00 00 48
0f be 55 01 48
この結果を見て、頭の中が「???」となりました。というのも、Plamo Linuxでは共有ライブラリを含むパッケージを安全に更新するために、パッケージ管理用スクリプトはstatic linkした専用のコマンド を使うようにしているからです。
これら専用のコマンドは/sbin/installer/以下に収めてあり、確認すると、"ls"もきちんとにstatic linkになっています。
$ ls /sbin/installer
[* bzip2* cp* dirname* gzip* mv* rmdir* tar* wc*
basename* cat* cut* echo* ln* paste* sed* tee* xz*
busybox* chmod* date* expr* ls* readlink* sh* touch* xzcat*
bzcat* comm* dialog* grep* mkdir* rm* sort* uniq* zcat*
$ file /sbin/installer/ls
/sbin/installer/ls: ELF 64-bit LSB executable, x86-64,
version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32,
BuildID[sha1]=da04378ba94777f445f236eca25143f6f2300b32, stripped
$ ldd /sbin/installer/ls
動的実行ファイルではありません
static linkなバイナリは実行時に共有ライブラリを必要としません。それにもかかわらず、SegFaultは共有ライブラリlibc-2.32.soの中で発生しています。どうしてそんなことが起きるのだろう、と首を捻( ひね ) りつつ、調べてみることにしました。
分析
トラブル状況を調べるために/sbin/installer/lsをあれこれイジってみたところ、たいていの機能は動作するものの、ファイルサイズ等の詳細情報を表示する"-l "オプションを指定するとエラーになることがわかりました。
$ /sbin/installer/ls
Desktop Mail Public Videos libc-2.32-x86_64-B2.txz
Documents Music Sample Work glibc-2.32-x86_64-B2.txz
...
$ /sbin/installer/ls -F
Desktop/ Mail/ Public/ Videos/ libc-2.32-x86_64-B2.txz
Documents/ Music/ Sample/ Work/ glibc-2.32-x86_64-B2.txz
..
$ /sbin/installer/ls -i
57487674 Desktop 21013232 Pictures 87692 Work
57487675 Documents 37874572 Public 37874576 glibc-2.32-x86_64-B2.txz
4855301 Downloads 37871997 Sample 37874577 libc-2.32-x86_64-B2.txz
...
$ /sbin/installer/ls -l
Segmentation fault
幸いなことに、このトラブルは実機ではなく仮想環境(VirtualBox)で生じたので、とりあえずglibcを更新する前の状態までロールバックして、条件を絞りこむことにしました。
まず一つ前のバージョンであるglibc-2.31ではこの現象は発生しなかったので、どうやら2.32の段階で侵入した問題のようです。
次に「glibcの後方互換性の問題かな?」と考え、glibc-2.32な環境で同じコマンドをビルドしてみることにしました。
このトラブルのせいでパッケージ管理ツールは使えないものの、Plamo Linuxのパッケージはtar形式をxzで圧縮しているだけなので、たいていのパッケージはそのまま展開するだけでも機能します。
そこでglibc-2.32に更新してupdatepkgが動かなくなった環境に、libc-2.32パッケージを手動で展開してみたところGCCが動くようになり、この環境でlsをビルドし直したところ、static linkな状態でも問題なく動作します。
ところが、このstatic linkしたlsは、glibc-2.31以前の環境に持ってゆくと"Assertion failure"なエラーになります。
$ ./ls_static -l
total 25264120
ls_static: dl-call-libc-early-init.c:37: _dl_call_libc_early_init:
Assertion `sym != NULL' failed.
中止
どうやらglibc-2.32で作ったstatic linkなバイナリをglibc-2.31以前に持って行くのもダメなようです。
もう少し詳しい情報が得られないかな、と考え、glibc-2.30環境でデバッグ情報付きのバイナリを作り、glibc-2.32環境に持ち込んで、デバッガ(gdb)を試してみました。
$ file ./ls_static_glibc230
./ls_static_glibc230: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux),
statically linked, BuildID[sha1]=dffd8c4e274ec29fc18b181898b06fc7e99ca57a,
for GNU/Linux 3.2.0, with debug_info, not stripped
$ gdb ./ls_static_glibc230
GNU gdb (GDB) 8.0
Copyright (C) 2017 Free Software Foundation, Inc.
....
Reading symbols from ./ls_static_glibc230...done.
(gdb) run -l
Starting program: /home/kojima/ls_static_glibc230 -l
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff77d7f90 in __nss_readline () from /lib/libc.so.6
(gdb)
この結果を見ると、libc.so.6の"__nss_readline"という関数の中でSegFaultしているようです。一方、static linkのlsを調べると"__nss_XXXX"というシンボルはいくつかあるものの、"__nss_readline"は存在しません。
$ nm ls_static_glibc230 | grep __nss
000000000051d8e8 V __nss_aliases_database
0000000000479be0 T __nss_configure_lookup
000000000051e3e0 B __nss_database_custom
0000000000479740 T __nss_database_lookup2
000000000051d8e0 V __nss_ethers_database
000000000051d8d8 V __nss_group_database
000000000047a230 T __nss_group_lookup2
000000000051d8d0 V __nss_gshadow_database
00000000004b3840 T __nss_hash
000000000051d8c8 V __nss_hosts_database
000000000051d8c0 V __nss_initgroups_database
000000000047a080 T __nss_lookup
0000000000479d10 T __nss_lookup_function
000000000051d8b8 V __nss_netgroup_database
000000000051d8b0 V __nss_networks_database
000000000047a130 T __nss_next2
000000000051e410 B __nss_not_use_nscd_group
000000000051e3f0 B __nss_not_use_nscd_passwd
000000000051d8a8 V __nss_passwd_database
000000000047a2c0 T __nss_passwd_lookup2
000000000051d8a0 V __nss_protocols_database
000000000051d898 V __nss_publickey_database
000000000051d890 V __nss_rpc_database
000000000051d888 V __nss_services_database
000000000051d880 V __nss_shadow_database
どうやら"__nss_readline"がこの問題の鍵っぽい、と考えて、glibcのChangeLogを調べてみると、2020-07-21付けで"__nss_readline"という新しい関数(New function)が入った、旨の記載がありました。
2020-07-21 Florian Weimer <fweimer@redhat.com&lgt;
COMMIT: bdee910e88006ae33dc83ac3d2c0708adb6627d0
nss: Add __nss_fgetent_r
* include/nss_files.h: Modified.
(libc_hidden_proto): Modified.
(libc_hidden_proto): Modified.
(libc_hidden_proto): Modified.
(libc_hidden_proto): Modified.
(__nss_readline): New function.
(__nss_readline_seek): New function.
(__nss_parse_line_result): New function.
(libc_hidden_proto): New.
(libc_hidden_proto): New.
(__nss_fgetent_r): New function.
* nss/Makefile: Modified.
* nss/Versions: Modified.
* nss/nss_fgetent_r.c: New file.
* nss/nss_files/files-XXX.c: Modified.
(internal_getent): Modified function.
* nss/nss_parse_line_result.c: New file.
* nss/nss_readline.c: New file.
どうやら、glibc-2.32にnss/nss_readline.cというファイルが追加され、NSS回りの構造が変ったことがこの問題の原因のようなものの、そもそもstatic linkなバイナリが、なぜ共有ライブラリ(libc.so.6)を使おうとするのかは謎のままです。
解決しないまま日々が過ぎ、glibc-2.32への更新はPlamo-8まで見送って、7.xはglibc-2.31で停めておこうか、、とも思うようになりました。
(続く)
紙幅の都合で思わせぶりなところで終ったものの、この問題はすでに解決済みで、Plamo-MLに報告したように、Plamo-7.x環境でglibc-2.32を試してみたい人は、ftp.plamo.linet.gr.jpの/pub/Plamo-test/for-7.x/x86_64/にあるpkgtools7-1.0-x86_64-B1.txzを先にインストールしてください。
なぜこのパッケージを先にインストールしなければいけないかを含め、このトラブルの原因と対策は次回詳しく紹介するのでご期待ください。