Raspberry Piのy2038(2038年問題)を確認してみたメモ

サーバーサイドでは、見ることほとんど無くなった32bit Linux。

ただ、最近使うこと多くなっているRaspberry PiのOSである、Raspberry Pi OS(旧 Raspbian)は32bit Linuxです。

もし、Raspberry Piを配置して今後18年動き続ければ、2038年問題引っかかります。(可能性低そうですが)

ということで少し追っておきました。

2038年問題

2038年問題ですが、Wikipediaから引用です。 y2038 と書かれていたりもします。

協定世界時における1970年1月1日0時0分0秒(日本標準時では1970年1月1日9時0分0秒)から2,147,483,647秒を経過した、2038年1月19日3時14分7秒(日本標準時では2038年1月19日12時14分7秒、閏秒は考慮していない)を過ぎると、この値がオーバーフローし、負と扱われる[注釈 2]ため、もし時刻を正しく扱えていることを前提としたコードがあれば、誤動作する。

Wikipediaより引用

要は時間に使っている time_t という32bit型が桁あふれします。

溢れるタイミングは、 2038 年 1 月 19 日 03:14:07 でUNIX時間で言えば 2,147,483,647秒 です。

この 2147483647 という数字、少しPython使いながら見てみます。 2進数で見るとわかりやすいですが、桁あふれ直前の数字となります。

>>> print(f"{2147483647:032b}") #32桁の2進数表示
01111111111111111111111111111111
>>> print(datetime.datetime.fromtimestamp(2147483647))
2038-01-19 12:14:07

2147483647 に1を足して 2147483648とすると、32bit目のMSB(most Significant Bit)と呼ばれるビットに1が立ちこれがマイナスを表すようになります。

1901 年 12 月 14 日 05:45:52

#-> 4バイトのlongに入れると最上位ビットが立って-2147483648を示すようになる
>>> print(f"{2147483648:032b}")
10000000000000000000000000000000


# -2147483647なのでずっと昔の日付になってしまう
>>> print(datetime.datetime.fromtimestamp(-2147483647))
1901-12-14 05:45:52

実際にRapberry Pi OS(32bit OS)で試してみる

利用しているバージョンは以下です。

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 5.4.51-v7+ #1333 SMP Mon Aug 10 16:45:19 BST 2020 armv7l GNU/Linux

桁あふれの確認

以下のコマンドを実行してみます。 time_t 型に 2147483648 を代入してみています。

cat <<EOF > a.c && gcc a.c && ./a.out
#include<stdio.h>
#include <sys/time.h>
int main(){
 time_t t=2147483648;
 printf("%ld\n", t);
}
EOF

WSLのUbuntu上(当然64bit Linux)で実行すると 2147483648 が表示されます。

Raspberry Pi上で実行すると、以下の通り -2147483648 になってしまうことがわかります。

f:id:yomon8:20200921085240p:plain

該当時間まで時間設定を進めてみる

該当の時間になると、実際にどういうことが起きるのか見てます。

まず、時間の同期をしているサービスを停止します。

sudo systemctl daemon-reload
sudo systemctl stop systemd-timesyncd

その上で、桁あふれ直前の時間に設定して挙動を見てみます。

# 時刻を10秒前に設定
pi@raspberrypi:~ $ sudo date -s "01/19 3:13:57 2038"
Tue 19 Jan 03:13:57 UTC 2038

# まだ大丈夫
pi@raspberrypi:~ $ date +%s
2147483639

# まだ大丈夫
pi@raspberrypi:~ $ python3 -c "import datetime; print(datetime.datetime.now());"
2038-01-19 03:14:01.298023

# おかしくなった!
pi@raspberrypi:~ $ date
Fri 13 Dec 20:46:10 UTC 1901
pi@raspberrypi:~ $ date +%s
-2147483585

# time_tがOverflowしているエラーが出たりする
pi@raspberrypi:~ $ python3 -c "import datetime; print(datetime.datetime.now());"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
OverflowError: timestamp out of range for platform time_t

ということで、当たり前ではあるのですが現バージョンのRaspberry PiのOSも2038年問題にかかることがわかりました。

Raspberry Pi OS 64bitなら大丈夫

数ヶ月前に出た以下の記事の通り、Raspberry Pi OSの64bit版が出ました。と言ってもまだBeta版です。

Raspberry Pi OS (64 bit) beta test version - Raspberry Pi Forums

こちらからダウンロードできます。

downloads.raspberrypi.org

MicroSDに書き込んで起動します。今使っているのはこのバージョンです。

pi@raspberrypi:~ $ uname -a
Linux raspberrypi 5.4.51-v8+ #1333 SMP PREEMPT Mon Aug 10 16:58:35 BST 2020 aarch64 GNU/Linux

これを使って問題が解消されるか見てみます。

まずは time_t の桁あふれ無くなっているか確認。大丈夫そう。

f:id:yomon8:20200921090710p:plain

問題の時間を超えても正常に動作していますので、大丈夫そう。

# 時刻を10秒前に設定
pi@raspberrypi:~ $ sudo date -s "01/19 3:13:57 2038"
Tue 19 Jan 03:13:57 UTC 2038

# 問題の時間超えたけど大丈夫
pi@raspberrypi:~ $ date +%s
2147483677
pi@raspberrypi:~ $ python3 -c "import datetime; print(datetime.datetime.now());"
2038-01-19 03:14:50.028553

さいごに

しかし、冷静になると2038年となると18年後ですね。流石に32bitのRaspberry Piは動いて無さそうだなと思いながらも確認してみたい欲求から動かして確認してしまいました。

また、32bitのまま対応する対応もされているようなので、今のRaspberry Pi OSのバージョンでは動かないにしろ32bit Linuxが存続し続けるのかもしれません。

japan.zdnet.com

何にしろ、2038年となると18年後なので、私も50代半ば。その時に何やっているかは全くわからないですが、少し将来を考えるきっかけになりました。

(おまけ) settimeofday()は2232年までしか設定できない

64bitのRaspberry Piでずっと先の9999年の未来を設定してみたら、以下のようなエラーが出ました。

pi@raspberrypi:~ $ sudo date -s "12/31 23:59:59 9999"
date: cannot set date: Invalid argument
Fri 31 Dec 23:59:59 UTC 9999

どうも 2232 年 4 月 18 日 23:47:15 に境界がありそう。

pi@raspberrypi:~ $ sudo date -s "04/18 23:47:15 2232"
Wed 18 Apr 23:47:15 UTC 2232
pi@raspberrypi:~ $ sudo date -s "04/18 23:47:16 2232"
date: cannot set date: Invalid argument
Wed 18 Apr 23:47:16 UTC 2232

この 8277292036 という数字が境界になってそう。

pi@raspberrypi:~ $ sudo strace date -s "04/18 23:47:16 2232"
# 省略
clock_settime(CLOCK_REALTIME, {tv_sec=8277292036, tv_nsec=0}) = -1 EINVAL (Invalid argument)
settimeofday({tv_sec=8277292036, tv_usec=0}, NULL) = -1 EINVAL (Invalid argument)
# 省略

やっぱりここが境界になって settimeofday() がエラーになるようです。

cat <<EOF > a.c && gcc a.c && sudo strace ./a.out
#include <stdio.h>
#include <sys/time.h>

int main(void)
{
  struct timeval t0, t1;
  t0.tv_sec = 8277292035;
  t0.tv_usec = 0;
  t1.tv_sec = 8277292036;
  t1.tv_usec = 0;
  settimeofday(&t0, NULL);
  settimeofday(&t1, NULL);
}
EOF

# strace結果--抜粋
settimeofday({tv_sec=8277292035, tv_usec=0}, NULL) = 0
settimeofday({tv_sec=8277292036, tv_usec=0}, NULL) = -1 EINVAL (Invalid argument)
# strace結果--抜粋

ソース見たらこんな記載がありました。

/*
 * Limits for settimeofday():
 *
 * To prevent setting the time close to the wraparound point time setting
 * is limited so a reasonable uptime can be accomodated. Uptime of 30 years
 * should be really sufficient, which means the cutoff is 2232. At that
 * point the cutoff is just a small part of the larger problem.
 */

time64.h « linux « include - kernel/git/stable/linux.git - Linux kernel stable tree

2232年なんて流石に生きてないはずですし、当然32bitのLinuxが動いてない、またはこのソースで動いているシステムも無い・・と思うので。ただの豆知識です。