目次
■クラウド&インフラストラクチャ
2019.11.26 2024.02.20 約3分
本連載では、インターネット基盤技術、または、インターネットのインフラ技術と呼ばれる領域に関して、Webホスティングサービスの歴史やWebサーバの設計と実装を中心に執筆していきます。第7回では、これまで述べてきた基礎概念から少し踏み込んで、マルチテナント環境において非常に重要となるセキュリティの研究動向について、第6回に引き続き様々な観点から紹介し、その課題をまとめました。第8回では、いよいよ前回整理した課題をうまく解決するアーキテクチャとその実装を紹介します。
連載一覧:まつもとりーのインフラ入門
高集積にホストを収容するマルチテナント環境において、Webサーバプロセスは複数のホストに対するリクエストを、プロセスを使いまわしながら処理してレスポンスを返します。 その際に、Linux Capabilitiesといった特権を与えられたサーバプロセスは、root権限で実行されていなくても、setuid()およびsetgid()システムコールを実行可能となります。 その後、mod_suid2同様にApacheのサーバプロセス自体を任意のuid、gidに権限変更してから処理を実行し、再度、元のuid、gidに戻します。
この仕組みによって、mod_phpのようなDSO実行方式であっても、PHPスクリプトは権限が異なるため他ホスト領域を閲覧できません。また、実行後でも、元のサーバプロセスの権限に戻すことで、プロセスの再利用も可能にしているため、DSO実行方式の性能を維持できます。
しかし、このようなプロセスは、rootのように全ての特権を持たないものの、setuid()およびsetgid()システムコールを実行できる特権を保持していることになります。すなわち、それが意味するところはWebアプリケーションの脆弱性をつかれ悪意のある者に乗っ取られた場合、setuid()およびsetgid()システムコールによる権限変更を利用し、他ホスト領域のファイル閲覧や変更および不正プログラムの配置や配布等が可能となります。
つまり、サーバプロセス自体に権限を変更できる特権の保持を許したままレスポンスを生成することは、同時に数多くの脆弱性を許すことになります。
一方で、setuid()およびsetgid()システムコールを実行した後にCAP_SETUIDおよびCAP_SETGIDのLinux Capabilitiesを放棄し、処理後にプロセスを復帰できないように改修すれば安全になりますが、やはりそれではワーカプロセスが再利用できなくなり、mod_suid2同様性能は著しく低下することになります。
一般に、サーバプロセスにアクセス制御を設定後に再度解除するというアプローチは性能上の利点を得られますが、共有型の大規模Webホスティング基盤のセキュリティを考える上でリスクが非常に大きく、脆弱性をつかれた場合の利用者や閲覧者への被害は甚大であり、避けるべきと考えられます。
DSO方式の利点は、プログラムを高速に実行できることです。そのため、DSO方式のアクセス制御アーキテクチャを設計する上では、性能劣化を十分考慮しなければなりません。
例えば、これまで述べてきたようなCGI実行方式のためのアクセス制御モジュールであるsuEXECのようなプログラム実行時に新たに子プロセスを生成し、コンテンツ処理後にプロセスを破棄するアーキテクチャは、性能を大幅に低下させることにつながります。
また、mod_ruid2のように、プロセスを生成せずにサーバプロセスに権限変更の特権を与えてプロセスを再利用すれば高速に実行できるが、前回述べた通り、 脆弱性が生じます。
そこで、松本らは、Linux上で動作することを前提とし、Linuxにおけるスレッドをpthread_create()関数によって一時的に生成し、そのスレッド上で権限分離を行った後、スレッド配下でプログラムの処理を行い、最後にスレッドを破棄する手法mod_process_securityを提案しています。
Linuxにおけるスレッドはプロセス内の同一メモリ空間上で実行でき、メモリ消費量等が軽減できます。また、Linux上では、下記の論文で示されているように、スレッドの生成・破棄はプロセスの生成・破棄よりも処理が軽くなります。
スレッドの生成・破棄を利用することにより、サーバプロセスを破棄する必要もありません。 下図にDSO実行方式にmod_process_securityを適用した場合の、処理の流れを示します。
Linux上で動作するApacheは、親サーバプロセス(Parent Server Process)から事前にfork()システムコールが実行され生成された複数の子サーバプロセス(Child Server Process)がリクエストを受け付けるために待機しています。
リクエストを受け付けると、子サーバプロセス上で一時的にスレッド(Control Thread)を生成します。一時的に生成したスレッドに対し、権限変更の特権であるLinux CapabilityのCAP_SETUID、CAP_SETGIDを付与します。この特権によって、スレッドは、スレッド単位で任意のuid、gidに権限変更可能となります。
その後、実行対象のプログラムのuig、gid等の権限情報を動的に取得して、その権限にスレッドの権限変更を行います。スレッドの権限変更を行った後は、プログラムを実行する前にスレッドに付与された特権を破棄しておくようにします。
これによって、mod_ruid2で生じたような、プログラム経由での権限変更を防止します。スレッド上で直接プログラムを実行した後は、スレッドを破棄して、スレッドが属した子サーバプロセスは再度リクエスト受け付けに再利用されます。
これによって、既存のDSO実行方式のアクセス制御アーキテクチャのように、サーバプロセスの生成破棄をすることなく、安全にアクセス制御を行えます。また、スレッドの生成、破棄の処理時間の短さから性能劣化を低減し、DSO実行方式の特徴である高い性能を維持できます。
実際にCGIとsuEXECを利用している場合と、mod_phpとmod_process_securityを利用している場合のシステムコールの回数を比較すると、その差が歴然であることがわかります。 以下のコマンドでシステムコールの回数をカウントしながら、実際にphpinfo()を実行するだけのコンテンツにリクエストを送って、レスポンスを受信してみました。
strace -c -f -p PID
すると、suEXECの場合は3377回のシステムコールが発行されているのに対し、mod_process_securityの場合は155回とその差は約20倍以上でした。
また、以下のようなコマンドで実行に時間がかかっているシステムコールを調査すると、実行に時間がかかっているのは、clone()、open()、close()、execve()など、CGIとsuEXEC実行時に一からインタプリタを立ち上げてプロセスを複製している処理に必要なシステムコールがほとんどでした。
cat cgi.log | grep -v epoll_wait | grep -v futex | perl -ane ‘/<([\d\.]+)>/; print $_ if $1 > 0.00005;’
以上のことから、mod_phpにしてインタプリタをApache httpdに直接組み込んだ上で、スレッド単位で権限分離することがどれほど効率的かが理解できます。
さらに、一日のアクセス数が約1000万アクセスあるようなホスティングサーバに対して、CGIとsuEXECで権限分離していたサーバとDSOとmod_process_securityで権限分離した場合のリソース使用量の違いを比較しました。
すると以下の図のように、最近の多くのコンテンツは動的コンテンツであり、PHPなどで動作することが多いため、その実行効率を上げるだけでサーバのリソース使用量、特にCPUの使用量に関しては大きく改善していることがわかりました。
特に、システムコールの数の比較でも大きな差があったように、システムコールのオーバーヘッドが大きく改善したことから、systemのCPU使用量が大きく減少していることがわかります。
今回は、前回まとめた高集積マルチテナント環境のセキュリティと性能要件に関する関連研究の課題に基づき、それらをうまく解決するアーキテクチャとその実装を紹介しました。この実装を利用することによって、実際のリソース使用量も大きく改善し、性能が大きく上がることも確認しました。 それでは、次回以降はこれまでのセキュリティやリソース、性能の観点を踏まえた上で、どのように効率的に運用保守していくかについて、関連研究を踏まえて紹介します。
本連載は下記の私が執筆した論文を参考に、新しい読者へ広めるために平易な形へと再編集しています。
・ 松本 亮介, 栗林 健太郎, 岡部 寿男, Webサーバの高集積マルチテナントアーキテクチャと運用技術, 電子情報通信学会論文誌B, Vol.J101-B, No.1, pp.16-30, Jan 2018.
・ copyright©2018 IEICE