glibcアップデートによるタイムゾーン変更
藤本です。 1ヶ月ぐらい前にFedora22が公開がされました。 時間がある時に触っていますが、 個人的にはyumがDNFに置き換わったことに注目しています。 DNFの機能というよりもLinuxもPythonのデフォルトバージョンが3系に置き換わるのではないかと期待せずにいられません。 yumコマンドも残されていますが、dnfコマンドのwrapperスクリプトとなっています。
さて、本題。
概要
今回はAmazon Linux(RHEL/CentOS含む)でタイムゾーンが変更された事例をご紹介します。 Amazon LinuxはAMIからデプロイするとデフォルトタイムゾーンはUTCです。 ただ東京リージョンで利用される場合、JST(UTC+9)に変更されている方はいらっしゃるのではないでしょうか。
今年の始めにglibcのGHOSTというバッファオーバーフローを引き起こす脆弱性が公開されました。 脆弱性対応としてglibcを2.17-55.93.amzn1以降にアップデートすることで対処が可能です。
この時にタイムゾーンが変化するという事象が発生する可能性があります。
発生条件は以下の2つが異なる時です。 - /etc/localtimeファイル - /etc/sysconfig/clockのZONEプロパティ
異なると言ってもわかりづらいですよね。詳細は対策の章で説明します。 発生条件を満たした状態でglibcをyumやrpmファイルからアップデートすると、 /etc/localtimeファイルが/etc/sysconfig/clockのZONEの値のzoneファイルに置き換えられます。
対象OS
- Amazon Linux
- Red Hat Enterprise Linux 5/6
- CentOS 5/6 ※ UbuntuやSUSEは未確認
対策
発生条件を避けるためには/etc/localtimeと/etc/sysconfig/clockのZONEプロパティを合わせてください。
これまた分かりづらいですね。 一つ一つ説明します。
/etc/localtime
/etc/localtimeファイルはOSがタイムゾーンを判断するzone情報が記載されたファイル(以下、zoneファイル)です。 zoneファイルは/usr/share/zoneinfo/配下に配置されています。 Amazon Linuxの場合、UTCのzoneファイルのコピーが配置されています。
# diff /etc/localtime /usr/share/zoneinfo/UTC
同一内容のファイルです。
# diff /etc/localtime /usr/share/zoneinfo/Asia/Tokyo Binary files /etc/localtime and /usr/share/zoneinfo/Asia/Tokyo differ
JSTではありません。
OSが利用するタイムゾーンを変更したい場合、 /usr/share/zoneinfo/配下のzoneファイルを/etc/localtimeにコピー、もしくはリンクを貼ることで変更することが可能です。
例. JSTに設定
# date Thu Jul 2 09:20:07 UTC 2015 # diff /etc/localtime /usr/share/zoneinfo/Asia/Tokyo Binary files /etc/localtime and /usr/share/zoneinfo/Asia/Tokyo differ # cp -a /usr/share/zoneinfo/Asia/Tokyo /etc/localtime cp: overwrite ‘/etc/localtime’? y # diff /usr/share/zoneinfo/Asia/Tokyo /etc/localtime # date Thu Jul 2 18:34:11 JST 2015
タイムゾーンがJSTに変更されました。
/etc/sysconfig/clockのZONEプロパティ
次に/etc/sysconfig/clockファイルはZONEプロパティに/usr/local/zoneinfo/以降のパスを指定することで、 glibcアップデート時に利用するzoneファイルを指定することができます。
例. UTC指定の場合
# ls /usr/share/zoneinfo/UTC /usr/share/zoneinfo/UTC # grep ZONE /etc/sysconfig/clock ZONE="UTC"
例. JST指定の場合
# ls /usr/share/zoneinfo/Asia/Tokyo /usr/share/zoneinfo/Asia/Tokyo # grep ZONE /etc/sysconfig/clock ZONE="Asia/Tokyo"
ちなみに私の勝手なイメージで/etc/sysconfig/配下はデーモン関連の設定ファイルが配置されていると思っていましたが、 このファイルに限って言えばデーモンに関連付いていません。 このファイルのZONEプロパティを変更したからと言ってOS再起動時にタイムゾーンが反映される訳ではありません。
まとめ
/etc/localzoneファイルを置き換える時、/etc/sysconfig/clockファイルのZONEプロパティの値も忘れずに書き換えましょう! GHOST対応に関わらず、発生条件を満たした状態でglibcをアップデートするとタイムゾーンが変更されます。 ご注意ください。
ちょっと深追い
なぜこのような事象が起きるのでしょうか。 最終的にバイナリのスクリプトファイルのソースにたどり着けず推測が入ります。
Q. glibcパッケージで何でタイムゾーン?
/etc/localtimeはglibcパッケージに含まれています。
# rpm -qf /etc/localtime glibc-2.17-55.143.amzn1.x86_64 # rpm -ql glibc |grep localtime /etc/localtime
このように/etc/localtimeはglibcパッケージに含まれているため、 glibcパッケージのインストール時、アップデート時に配布されています。
Q. /etc/localtimeファイルがないとタイムゾーンはどうなる?
UTCになります。
# date Thu Jul 2 18:48:36 JST 2015 # rm /etc/localtime rm: remove regular file ‘/etc/localtime’? y # date Thu Jul 2 09:48:44 UTC 2015
Q. ZONEの記載を誤ると/etc/localtimeファイルはどうなる?
そのままです。
# diff /usr/share/zoneinfo/Asia/Tokyo /etc/localtime # grep ZONE /etc/sysconfig/clock ZONE="AAA" # yum update glibc Loaded plugins: priorities, update-motd, upgrade-helper amzn-main/latest | 2.1 kB 00:00 amzn-updates/latest | 2.3 kB 00:00 Resolving Dependencies --> Running transaction check ---> Package glibc.x86_64 0:2.17-55.140.amzn1 will be updated --> Processing Dependency: glibc = 2.17-55.140.amzn1 for package: glibc-common-2.17-55.140.amzn1.x86_64 ---> Package glibc.x86_64 0:2.17-55.143.amzn1 will be an update --> Running transaction check ---> Package glibc-common.x86_64 0:2.17-55.140.amzn1 will be updated ---> Package glibc-common.x86_64 0:2.17-55.143.amzn1 will be an update --> Finished Dependency Resolution Dependencies Resolved ======================================================================================================================================= Package Arch Version Repository Size ======================================================================================================================================= Updating: glibc x86_64 2.17-55.143.amzn1 amzn-updates 5.7 M Updating for dependencies: glibc-common x86_64 2.17-55.143.amzn1 amzn-updates 28 M Transaction Summary ======================================================================================================================================= Upgrade 1 Package (+1 Dependent package) Total size: 34 M Is this ok [y/d/N]: y Downloading packages: Running transaction check Running transaction test Transaction test succeeded Running transaction Updating : glibc-2.17-55.143.amzn1.x86_64 1/4 Updating : glibc-common-2.17-55.143.amzn1.x86_64 2/4 Cleanup : glibc-2.17-55.140.amzn1.x86_64 3/4 Cleanup : glibc-common-2.17-55.140.amzn1.x86_64 4/4 Verifying : glibc-common-2.17-55.143.amzn1.x86_64 1/4 Verifying : glibc-2.17-55.143.amzn1.x86_64 2/4 Verifying : glibc-common-2.17-55.140.amzn1.x86_64 3/4 Verifying : glibc-2.17-55.140.amzn1.x86_64 4/4 Updated: glibc.x86_64 0:2.17-55.143.amzn1 Dependency Updated: glibc-common.x86_64 0:2.17-55.143.amzn1 Complete! # diff /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
/etc/localtimeファイルは変更ありません。
Q. そもそもこの挙動ってどういうこと?
勘がよい方なら配布するだけなら固定のzoneファイルになるんじゃないか、 と思われたのではないでしょうか? rpmファイルは展開時にポストスクリプトが実行されます。
# yumdownloader glibc Loaded plugins: priorities, update-motd, upgrade-helper (1/2): glibc-2.17-55.143.amzn1.i686.rpm | 6.2 MB 00:00 [root@ip-172-31-25-46 ~]# ls -l total 12180 -rw-r--r-- 1 root root 6453703 Jun 25 04:37 glibc-2.17-55.143.amzn1.i686.rpm -rw-r--r-- 1 root root 6006038 Jun 25 04:37 glibc-2.17-55.143.amzn1.x86_64.rpm # rpm -qp --scripts glibc-2.17-55.143.amzn1.x86_64.rpm postinstall program: /usr/sbin/glibc_post_upgrade.x86_64 postuninstall program: /sbin/ldconfig # file /usr/sbin/glibc_post_upgrade.x86_64 /usr/sbin/glibc_post_upgrade.x86_64: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.35, BuildID[sha1]=1fe8ebd70f1ef667e6b35ad485aa4c0523126d8e, stripped
glibcのrpmファイルであれば、 /usr/sbin/glibc_post_upgrade.x86_64が実行されます。 ただこのファイルがバイナリファイルで中身が読めませんが、 このスクリプト内で/etc/sysconfig/clockのZONEプロパティの値を読み込んで、 /etc/localtimeと比較した上で異なる時のみ/etc/localtimeを/etc/sysconfig/clockに対応したzoneファイルに置き換えているのではないかと考えています。
yumコマンドにより発行されたシステムコールでもそれぞれのファイルを読み込んでいる形跡があるので、 認識は合っているのではないかと。。
システムコール抜粋
# egrep 'glibc_post|localtime|sysconfig/clock' yum-update2.log open("/etc/localtime", O_RDONLY|O_CLOEXEC) = 5 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 lstat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 open("/etc/localtime", O_RDONLY) = 27 open("/etc/localtime", O_RDONLY) = 28 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 lstat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 open("/etc/localtime", O_RDONLY) = 31 open("/etc/localtime", O_RDONLY) = 32 open("/etc/localtime;5595171d", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 34 lstat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 rename("/etc/localtime;5595171d", "/etc/localtime") = 0 chown("/etc/localtime", 0, 0) = 0 chmod("/etc/localtime", 0644) = 0 utime("/etc/localtime", [2012/12/25-03:02:13, 2012/12/25-03:02:13]) = 0 open("/usr/sbin/glibc_post_upgrade.x86_64;5595171d", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 34 lstat("/usr/sbin/glibc_post_upgrade.x86_64", {st_mode=S_IFREG|0700, st_size=768736, ...}) = 0 lstat("/usr/sbin/glibc_post_upgrade.x86_64", {st_mode=S_IFREG|0700, st_size=768736, ...}) = 0 removexattr("/usr/sbin/glibc_post_upgrade.x86_64", "security.capability") = -1 ENODATA (No data available) rename("/usr/sbin/glibc_post_upgrade.x86_64;5595171d", "/usr/sbin/glibc_post_upgrade.x86_64") = 0 chown("/usr/sbin/glibc_post_upgrade.x86_64", 0, 0) = 0 chmod("/usr/sbin/glibc_post_upgrade.x86_64", 0700) = 0 utime("/usr/sbin/glibc_post_upgrade.x86_64", [2015/06/23-16:58:04, 2015/06/23-16:58:04]) = 0 stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=118, ...}) = 0 open("/etc/localtime", O_RDONLY|O_CLOEXEC) = 31 open("/etc/sysconfig/clock", O_RDONLY) = 34 open("/etc/localtime", O_RDONLY) = 34 open("/etc/localtime.tzupdate", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 34 stat("/etc/localtime.tzupdate", {st_mode=S_IFREG|0644, st_size=333, ...}) = 0 chmod("/etc/localtime.tzupdate", 0644) = 0 rename("/etc/localtime.tzupdate", "/etc/localtime") = 0 open("/var/spool/postfix/etc/localtime", O_RDONLY) = -1 ENOENT (No such file or directory) stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=333, ...}) = 0 open("/etc/localtime", O_RDONLY|O_CLOEXEC) = 31 lstat("/usr/sbin/glibc_post_upgrade.x86_64", {st_mode=S_IFREG|0700, st_size=768768, ...}) = 0 lstat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=333, ...}) = 0
ポストスクリプトが実行されて、 /etc/sysconfig/clockファイルをを読み込み、 /etc/localtime.tzupdateを/etc/localtimeにファイルを上書きしています。