このアプリ、gVisor 上でも動きますか?

※追記(5月1日)
想定していたシステムコールが未実装だった時、別のシステムコールにフォールバックして動作するアプリケーションも存在するとのご指摘をいただき、記事の一部(主に4章本文)を修正しました。ありがとうございます。

本稿では、 gVisor の Linux kernel との互換性問題によって動作に問題が生じる可能性のあるアプリを簡易的に判定する方法についてまとめます。

目次

  1. コンテナランタイムの概要
  2. gVisor の概要
  3. 現状の gVisor に出来ないこと
  4. 簡易的な判定方法
  5. おわりに

環境

  • OS: CentOS 7.7.1908
  • Nmap: version 6.4
  • gVisor: version release-20200323.0-181-g486759a37d61

1. コンテナランタイムの概要

まず前提として、コンテナ仮想化技術を実現する Docker などの多くのコンテナランタイムが持つ構造について説明します。コンテナランタイムは高レベルランタイムと低レベルランタイムの二層によって成り立っています。高レベルランタイムはユーザや、Kubernetes といったコンテナオーケストレータからの指示を受け取り、低レベルランタイムへコンテナの作成を指示します。指示を受け取った低レベルランタイムはコンテナの直接的な作成や管理を行います。

ちなみに、Docker にデフォルトで使用されているランタイムは以下の通りです。
・高レベルランタイム: containerd
・低レベルランタイム: runc

2. gVisor の概要

gVisor は、Google が開発する低レベルランタイムの一種です。

gVisor は runc と比べてどのような強みを持つランタイムなのでしょうか。最大の特徴は user-space kernel です。カーネルをホストと共有する runc とは異なり、 gVisor によって作成されるコンテナはユーザ空間で動作する特殊なカーネルによって動作します。これにより、コンテナ内で動作するアプリケーションの脆弱性を糸口にした攻撃の被害が、ホストにまで及ぶことを妨げています。ただし、 user-space kernel は完全な Linux kernl 互換ではありません。一部のシステムコールが未実装であるため、 gVisor 上での動作が叶わないアプリケーションも存在します。例えば 2018 年 11 月時点では sync_file_range が未実装であったため、 Postgresql が動作しませんでした。
https://github.com/google/gvisor/issues/88

3. 現状の gVisor に出来ないこと

gVisor の公式ページの記述によると、現時点で動作が確認されているアプリケーションと、サポートされているユーティリティは以下の通りです。
https://gvisor.dev/docs/user_guide/compatibility/

ユーティリティは ip や ss など一部のネットワーク関連のコマンドに制限がある他、grep は標準入力がパイプであり、標準出力が /dev/null でない場合動作するという制限があります。

未実装のシステムコールは以下の通りです。
https://gvisor.dev/docs/user_guide/compatibility/linux/amd64/
2020年4月23日時点では全 347 の Linux システムコールの内 251 が実装または一部機能を除き実装されている状態であり、 96 のシステムコールが未実装です。

では以上を踏まえ、gVisor で動作しないアプリケーションには何があるのでしょうか。公式ページには以下の文言があります。

The only real way to know if it will work is to try.
(それが機能するかどうか知る唯一にして真の方法は、試すことです。)

4. 簡易的な判定方法

実際問題、まだ発展途上な段階にある gVisor において動作しないアプリケーションを網羅するのは極めて難しいことです。では、アプリケーションが動作しなかった時、原因が必要なシステムコールを利用できないことにあるのか、それ以外の部分にあるのか、どのように切り分ければ良いでしょうか。今回は strace を使ってプロセスが使用するシステムコールについて調査し、その中に未実装なシステムコールが含まれるかどうか簡単に判定する方法について考えてみます。

※注意点
想定していたシステムコールが未実装であった時、別のシステムコールにフォールバックし代替手段で動作を続けるアプリケーションも存在します。この手法では、あくまで問題になる可能性のあるシステムコールが示されるに留まります。

それでは、実際に nmap に含まれるシステムコールを調査し、gVisor 上での動作が不可であるかどうか判定してみましょう。

① プロセスに含まれるシステムコールのリストを取得する。

$ strace -o rawdata nmap localhost && sed -e 's/(.*//' rawdata | sort | uniq > syscalls_nmap

任意のコマンドについて調査し、生成されるファイル名を変更したい場合は以下のように読み替えます。

$ strace -o 中間ファイル名 任意のコマンド && sed -e 's/(.*//' 中間ファイル名 | sort | uniq > 最終ファイル名

また、既に常駐しているプロセスについて調べたい場合は以下のように -p フラグを使ってPIDを指定します。

$ strace -o 中間ファイル名 -p PID && sed -e 's/(.*//' 中間ファイル名 | sort | uniq > 最終ファイル名

最終的な出力ファイルを確認すると、以下 40 のシステムコールが nmap localhost コマンドに使用されていることがわかりました。strace は gVisor 上でも動作するため、コンテナ内で動作するシステムコールのリストを取得することも可能ですが、正常に動作していないプロセスを対象に strace を実行すると、完全なリストは取得できません。ホスト上での実行をおすすめします。

$ nl syscalls_nmap
     1  +++ exited with 0 +++
     2  access
     3  arch_prctl
     4  bind
     5  brk
     6  close
     7  connect
     8  execve
     9  exit_group
    10  fcntl
    11  fstat
    12  futex
    13  getegid
    14  geteuid
    15  getgid
    16  getrlimit
    17  getsockname
    18  getsockopt
    19  getuid
    20  ioctl
    21  lseek
    22  mmap
    23  mprotect
    24  munmap
    25  open
    26  read
    27  readlink
    28  recvfrom
    29  recvmsg
    30  rt_sigaction
    31  rt_sigprocmask
    32  select
    33  sendmsg
    34  sendto
    35  set_robust_list
    36  set_tid_address
    37  setsockopt
    38  socket
    39  stat
    40  statfs
    41  write

② gVisor 未実装リストを作成する。

公式ドキュメントから gVisor の未実装システムコールのリストを取得します。
以下のリストを gvisor_unimplemented_20200423 として保存します。

msgget
msgsnd
msgrcv
msgctl
setfsuid
setfsgid
uselib
personality
ustat
sysfs
sched_setparam
sched_rr_get_interval
vhangup
modify_ldt
pivot_root
sysctl
adjtimex
acct
settimeofday
swapon
swapoff
reboot
iopl
ioperm
create_module
init_module
delete_module
get_kernel_syms
query_module
quotactl
nfsservctl
getpmsg
putpmsg
afs_syscall
tuxcall
security
set_thread_area
get_thread_area
lookup_dcookie
epoll_ctl_old
epoll_wait_old
remap_file_pages
semtimedop
vserver
mq_open
mq_unlink
mq_timedsend
mq_timedreceive
mq_notify
mq_getsetattr
kexec_load
add_key
request_key
keyctl
ioprio_set
ioprio_get
migrate_pages
set_robust_list
get_robust_list
vmsplice
move_pages
perf_event_open
fanotify_init
fanotify_mark
name_to_handle_at
open_by_handle_at
clock_adjtime
setns
process_vm_readv
process_vm_writev
kcmp
finit_module
sched_setattr
sched_getattr
renameat2
kexec_file_load
bpf
userfaultfd
membarrier
copy_file_range
pkey_mprotect
pkey_alloc
pkey_free
io_pgetevents
pidfd_send_signal
io_uring_setup
io_uring_enter
io_uring_register
open_tree
move_mount
fsopen
fsconfig
fsmount
fspick
pidfd_open
clone3

③ 2つのリストを比較し、未実装システムコールを表示する。
comm コマンドに -1 -2 フラグを付与することで、1番目のファイルにのみ存在する行と、2番目のファイルにのみ存在する行を非表示にします。これにより、nmap に用いられており gVisor に実装されていないシステムコールのみが表示されることになります。

$ comm -1 -2 syscalls_nmap gvisor_unimplemented_20200423
set_robust_list

実行結果から、nmap localhost コマンドには gVisor で未実装システムコール set_robust_list が含まれることを確認できました。実行結果から、nmap localhost コマンドには gVisor で未実装システムコール set_robust_list が含まれることを確認できました。よって、別のシステムコールにフォールバックされなかった場合、アプリは動作しないことになります。

5. おわりに

アプリケーションが動作しない原因が未実装のシステムコールではなく、実装が部分的であることに起因する場合は、さらに深い調査が必要になります。システムコールに問題がなければ、アプリケーションの使い方に問題があるか、未解決のバグが原因であると考えられます。互換性に関する既知のバグは以下から確認できます。

https://github.com/google/gvisor/issues?q=is%3Aissue+is%3Aopen+label%3A%22area%3A+compatibility%22

未知のバグが原因であった場合は、イメージの実行に使用したコマンドとデバッグログを提供し、github 上で報告することで OSS への貢献につながるかもしれません。

参考文献

  • 「strace(1) – Linux man page」 https://linux.die.net/man/1/strace (参照 2020年4月23日)
  • 徳永航平「すぐにわかる 直感 図解 Docker コンテナランタイム」『Software Design』2020年2月号、pp.59-80

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です