glibcアップデートによるタイムゾーン変更

2015.07.02

藤本です。 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にファイルを上書きしています。