Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Linuxの2038年問題を調べてみた

Linuxの2038年問題の現状調査の第一弾

  • Be the first to comment

  • Be the first to like this

Linuxの2038年問題を調べてみた

  1. 1. Research for y2038
  2. 2. はじめに • 2038年問題がそろそろ気になる時期になってきたので、調べ始めました • Linux Kernelの状況とユーザランドの整合がとれないといけない部分なので、かなりの長丁 場です • 今回は、カーネルの中を見てみました
  3. 3. Linuxの2038年問題に関係するコンポーネント • 多くのOSS/ユーザプログラム • 内部での時刻の取り扱いは、それぞれの実装に依存 • 各プログラミング言語の時刻機能を • libcが提供するデータ型、システムコール、ライブラリを用い て機能を実現する • ローカルタイムのはglibcが扱う • タイムゾーン設定に基づいて、GMT/UTCとローカルタイム の相互変換を担当 • 時刻はLinux Kernel内部で管理されている • MONOTONIC時刻を担当(Power ON起点の時刻) • カレンダー時刻を担当(カーネルはGMT/UTCのみを扱う) Linux Kernel Libc (glibc) Runtime library (ex. libstdc++) Generic OSS
  4. 4. Linux Kernelの2038年問題 • UNIXの時刻表現が、1970年1月1日0時0分0秒を起点とした秒カウンタになっており、その カウンタが符号つき32bitであることから生じる問題 • 符号つき32bitは2,147,483,647を超えた時点でがオーバーフローするため、2038年1月19日 3時14分7秒以降の時刻を表現できないことから、2038年問題と呼ばれる • Linuxには、この秒カウンタのデータ型time_tが2種類存在する • 32bitアーキテクチャでは符号つき32bit • 64bitアーキテクチャでは符号つき64bit • 一般に、time_tが符号つき64bitである64bitアーキテクチャでは、2038年問題が発生しない と言われている • 計算上は約3000億年まで対応できる
  5. 5. Linux Kernelの分析 • 2020年に、32bit環境の2038年問題対応の最後のパッチがメインラインにマージされた • https://lkml.org/lkml/2020/1/29/355?anz=web • この対応は5.4LTSにもマージされている • いつからはじまったのか? • 32bit環境の2038年問題は、3.17から開始された • https://www.deepl.com/translator#en/ja/Year%202038%20preparations%20in%203.17 • これらの歴史を紐解いていく • 分析するカーネルバージョン Kernel Version Release Year 3.10 2013 32bit環境の2038年問題対応が始まる前のLTSカーネル 4.14 2017 5.4のふたつ前のLTSカーネル 4.19 2018 5.4のひとつ前のLTSカーネル(CIPのSLTSカーネルでもある) 5.4 2019 32bit環境の2038年問題対応が完了したとされるカーネル
  6. 6. Linux Kernelの分析-5.4の状況 • Linuxの時刻設定システムコール • stime : 秒制度のUNIX time設定システムコール • Kerne 5.4では、time_tをそのまま受け渡すstimeと後方互換性のためのstime32が用意されている • kernel/time/time.c • ただし、64bit time_tが使えるstimeは一部のアーキテクチャ以外では無効化されている • settimeofday : 旧形式のシステムコールで、timeval (秒+マイクロ秒)で時刻設定を行う • Kerne 5.4では、 time_tをそのまま受け渡す実装と後方互換性のためのCOMPAT実装が用意されている • kernel/time/time.c • clock_settime : 新形式のシステムコールで、timespec (秒+ナノ秒)で時刻設定を行う • Kerne 5.4では、 ユーザ空間とのやり取りを__kernel_timespecで行うため、32bit環境でも64bitの秒カウンタを使うこと ができ、カーネル空間では2038年問題が発生しない • kernel/time/posix-timers.c • Linuxではtime_tがlong型なので、32bitでは4byte、64bitでは8byteで扱われる • 5.4時点でのシステムコール実装は、stime, settimeofdayでは32bit環境で2038年問題を抱えており、 clock_settimeのみ対策されている
  7. 7. 時刻設定の仕組み調査 • 32bitのarmカーネルを対象にシステムコール拡張を分析 • 調査のカギとなるパッチは「y2038: add 64-bit time_t syscalls to all 32-bit architectures」と思われる • https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v5.4.94&id=48166e6ea47d23984f0b 481ca199250e1ce0730a • このパッチでは、32bitアーキテクチャであるarm, m68k, x86などに対してシステムコールテーブルの追加が行われている ので、これをもとに実装を追跡する diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl index a96d9b5ee04e3..9016f4081bb9c 100644 --- a/arch/arm/tools/syscall.tbl +++ b/arch/arm/tools/syscall.tbl @@ -416,3 +416,24 @@ 399 common io_pgetevents sys_io_pgetevents_time32 400 common migrate_pages sys_migrate_pages 401 common kexec_file_load sys_kexec_file_load +# 402 is unused +403 common clock_gettime64 sys_clock_gettime +404 common clock_settime64 sys_clock_settime +405 common clock_adjtime64 sys_clock_adjtime +406 common clock_getres_time64 sys_clock_getres +407 common clock_nanosleep_time64 sys_clock_nanosleep +408 common timer_gettime64 sys_timer_gettime +409 common timer_settime64 sys_timer_settime +410 common timerfd_gettime64 sys_timerfd_gettime +411 common timerfd_settime64 sys_timerfd_settime +412 common utimensat_time64 sys_utimensat +413 common pselect6_time64 sys_pselect6 +414 common ppoll_time64 sys_ppoll +416 common io_pgetevents_time64 sys_io_pgetevents +417 common recvmmsg_time64 sys_recvmmsg +418 common mq_timedsend_time64 sys_mq_timedsend +419 common mq_timedreceive_time64 sys_mq_timedreceive +420 common semtimedop_time64 sys_semtimedop +421 common rt_sigtimedwait_time64 sys_rt_sigtimedwait +422 common futex_time64 sys_futex +423 common sched_rr_get_interval_time64 sys_sched_rr_get_interval 事前調査の結果、32bit環境のシステムコール実装が2038 年問題を抱えているように見えたstime, settimeofdayは拡張 されていない。 32bit環境のシステムコール実装が2038年問題を解決してい るように見えたclock_settimeに関しては、新しいシステムコー ルであるclock_settime64とつながっている 他もあわせて計21種類の64bit time_tに対応したシステム コールが用意されている
  8. 8. 時刻設定の仕組み調査 • 64bitと32bitのシステムコール呼び出しの違い • 32bitの場合 • clock_settime (syscall num. 262)は、32bit time_tを使う 実装(sys_clock_settime32)を呼び出す • 新しいclock_settime64(syscall num. 404)は、64bit time_tを使う実装(sys_clock_settime)を呼び出す • stimeとsettimeofdayは、どの実装も32bit time_tしか扱え ないので、呼び出す関数に意味はない • 廃止予定でないシステムコールのみ64bit time_t対応した と考えられる • 64bitの場合 • clock_settime/clock_settime64ともに、64bit time_tを使 う(sys_clock_settime)を呼び出す • settimeofdayは、64bitユーザランドの場合は64bit time_t を使う実装(sys_settimeofday)を、32bitユーザランドの場 合は32bit time_tを使う実装(compat_sys_settimeofday) を呼び出す • stimeは、既に廃止扱いになっているシステムコールだから か、syscall numすら割り当てられていない Syscall num. Syscall name Syscall impl. 25 stime sys_stime32 79 settimeofday sys_settimeofday 262 clock_settime sys_clock_settime32 404 clock_settime64 sys_clock_settime Syscall num. Syscall name Syscall impl. 112 clock_settime sys_clock_settime 170(64) settimeofday sys_settimeofday 170(32) settimeofday compat_sys_settimeofday 404 clock_settime64 sys_clock_settime
  9. 9. 時刻設定の仕組み調査 • システムコール呼び出しから、実処理までを追跡 • カレンダー時刻は、最終的にはdo_settimeofday64を呼び出す • do_settimeofday64まで、時刻はtimespec64/timespecで扱われる do_settimeofday64 stime stime32 do_sys_settimeofday64 settimeofday settimeofday(compat) posix_clock_realtime_set clock_settime clock_settime32
  10. 10. 時刻設定の仕組み調査 • 時刻設定システムコールは、最終的には do_settimeofday64を呼び出す • 名前が示す通り、この関数は64bitで時間を取り扱 う int do_settimeofday64(const struct timespec64 *ts) { struct timekeeper *tk = &tk_core.timekeeper; struct timespec64 ts_delta, xt; unsigned long flags; int ret = 0; if (!timespec64_valid_settod(ts)) return -EINVAL; raw_spin_lock_irqsave(&timekeeper_lock, flags); write_seqcount_begin(&tk_core.seq); timekeeping_forward_now(tk); xt = tk_xtime(tk); ts_delta.tv_sec = ts->tv_sec - xt.tv_sec; ts_delta.tv_nsec = ts->tv_nsec - xt.tv_nsec; if (timespec64_compare(&tk->wall_to_monotonic, &ts_delta) > 0) { ret = -EINVAL; goto out; } tk_set_wall_to_mono(tk, timespec64_sub(tk->wall_to_monotonic, ts_delta)); tk_set_xtime(tk, ts); out: timekeeping_update(tk, TK_CLEAR_NTP | TK_MIRROR | TK_CLOCK_WAS_SET); write_seqcount_end(&tk_core.seq); raw_spin_unlock_irqrestore(&timekeeper_lock, flags); /* signal hrtimers about time change */ clock_was_set(); if (!ret) audit_tk_injoffset(ts_delta); return ret; } typedef __s64 time64_t; struct timespec64 { time64_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
  11. 11. 時刻設定の仕組み調査 • 時刻設定システムコールは、最終的には do_settimeofday64を呼び出す • 名前が示す通り、この関数は64bitで時間を取り扱 う • timespec64の値をチェックする timespec64_valid_settodで有効値であることを 確認している • Linux内ではこれ以降㎱カウンタであるktime_tで 時刻を取り扱う • UNIX timeは64bitでも符号付きなので、負値は 異常値 • 64bitの UNIX timeよりもktime_tが先にオーバーフ ローするので、上限値を超えた場合は異常値とし て扱う • TIME_UPTIME_SEC_MAXの計算式を見ると、30 年連続動作を想定しているように見える • 2038年問題の次に、ktime_tのオーバーフロー 問題が存在している int do_settimeofday64(const struct timespec64 *ts) { struct timekeeper *tk = &tk_core.timekeeper; struct timespec64 ts_delta, xt; unsigned long flags; int ret = 0; if (!timespec64_valid_settod(ts)) return -EINVAL; raw_spin_lock_irqsave(&timekeeper_lock, flags); write_seqcount_begin(&tk_core.seq); timekeeping_forward_now(tk); xt = tk_xtime(tk); ts_delta.tv_sec = ts->tv_sec - xt.tv_sec; ts_delta.tv_nsec = ts->tv_nsec - xt.tv_nsec; if (timespec64_compare(&tk->wall_to_monotonic, &ts_delta) > 0) { ret = -EINVAL; goto out; } tk_set_wall_to_mono(tk, timespec64_sub(tk->wall_to_monotonic, ts_delta)); tk_set_xtime(tk, ts); out: timekeeping_update(tk, TK_CLEAR_NTP | TK_MIRROR | TK_CLOCK_WAS_SET); write_seqcount_end(&tk_core.seq); raw_spin_unlock_irqrestore(&timekeeper_lock, flags); /* signal hrtimers about time change */ clock_was_set(); if (!ret) audit_tk_injoffset(ts_delta); return ret; } static inline bool timespec64_valid_settod(const struct timespec64 *ts) { if (!timespec64_valid(ts)) return false; /* Disallow values which cause overflow issues vs. CLOCK_REALTIME */ if ((unsigned long long)ts->tv_sec >= TIME_SETTOD_SEC_MAX) return false; return true; } #define TIME_SETTOD_SEC_MAX (KTIME_SEC_MAX - TIME_UPTIME_SEC_MAX) #define KTIME_MAX ((s64)~((u64)1 << 63)) #define KTIME_SEC_MAX (KTIME_MAX / NSEC_PER_SEC) #define TIME_UPTIME_SEC_MAX (30LL * 365 * 24 *3600) static inline bool timespec64_valid(const struct timespec64 *ts) { /* Dates before 1970 are bogus */ if (ts->tv_sec < 0) return false; /* Can't have more nanoseconds then a second */ if ((unsigned long)ts->tv_nsec >= NSEC_PER_SEC) return false; return true; }
  12. 12. ktime_t はどのくらいもつのか? • ktime_t は64bitのnsカウンタ • typedef s64 ktime_t; なので、符号つきなのはtime_tと同じ • 1000 = 1024 = 2^10 で概算すると、64 – 10 – 10 – 10 = 34なので、34bitが秒に割り当たっている • 32bitのtime_tで約68年もつので、4倍の約272年もつ • 近似計算なので実際はもう少し持ちます • 時刻設定で連続稼働時間30年に耐えられるように設定可能な上限値を下げているため、実際には約242 年もつ計算になる • これらを総合すると、1970+242=2212年が限界値(近似計算による値)になる • 補足 • armなど一部のアーキテクチャを除くと、32bitアーキテクチャでは計算の高速化を狙ってか64bitのktime_tを 32bitの秒カウンタと32bitのnsカウンタに分けているものがあった • この場合はktime_tが2038年にオーバーフローしてしまう • 32bit環境でも、一律64bitの符号つきnsカウンタに統一する変更も行われている
  13. 13. その他の状況 • 時刻取得関係はktime_tからtime_tに変換される • 64bit環境では、ktime_tの上限がそのまま実装上の上限となる • 32bit環境では、clock_gettimeを除いてtime_tの上限が上限となる • タイムアウト関係も同様 • futexのタイムアウト設定は__kernel_timespecを使っているため、カーネル空間では2038年問題は発生しな い
  14. 14. LTSカーネルの状況 • 32bit環境で、2038年問題に部分的にでも対応するためには、 __kernel_timespecを使っ ている必要がある • 4.19のclock_settimeには使われているが、4.14のclock_settimeでは使われていない SYSCALL_DEFINE2(clock_settime, const clockid_t, which_clock, const struct timespec __user *, tp) { const struct k_clock *kc = clockid_to_kclock(which_clock); struct timespec64 new_tp; if (!kc || !kc->clock_set) return -EINVAL; if (get_timespec64(&new_tp, tp)) return -EFAULT; return kc->clock_set(which_clock, &new_tp); } SYSCALL_DEFINE2(clock_gettime, const clockid_t, which_clock, struct timespec __user *,tp) { const struct k_clock *kc = clockid_to_kclock(which_clock); struct timespec64 kernel_tp; int error; if (!kc) return -EINVAL; error = kc->clock_get(which_clock, &kernel_tp); if (!error && put_timespec64(&kernel_tp, tp)) error = -EFAULT; return error; } SYSCALL_DEFINE2(clock_settime, const clockid_t, which_clock, const struct __kernel_timespec __user *, tp) { const struct k_clock *kc = clockid_to_kclock(which_clock); struct timespec64 new_tp; if (!kc || !kc->clock_set) return -EINVAL; if (get_timespec64(&new_tp, tp)) return -EFAULT; return kc->clock_set(which_clock, &new_tp); } SYSCALL_DEFINE2(clock_gettime, const clockid_t, which_clock, struct __kernel_timespec __user *, tp) { const struct k_clock *kc = clockid_to_kclock(which_clock); struct timespec64 kernel_tp; int error; if (!kc) return -EINVAL; error = kc->clock_get(which_clock, &kernel_tp); if (!error && put_timespec64(&kernel_tp, tp)) error = -EFAULT; return error; } 4.14 4.19
  15. 15. LTSカーネルの状況 • 各バージョンの状況を見ると、一通り入っているとされている5.4を基準とすると • 4.19は頑張ればいけるかもレベル • 4.14はかなり無理がありそう • ただし、キーとなっていそうなこの対応は5.4にしか入っていないので、4.19もつらそうではある • https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v5.4.94&id=48166e6ea47d2 3984f0b481ca199250e1ce0730a • パッチの状況 • 5.4 • https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/log/?h=v5.4.94&qt=grep&q=y2038%3A • 4.19 • https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/log/?h=linux-4.19.y&qt=grep&q=y2038%3A • 4.14 • https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/log/?h=linux-4.14.y&qt=grep&q=y2038%3A
  16. 16. 次は • ユーザーランド。。。

×