blank
Linux Secure Virtual Hosting Extension

[目次]

[コンセプト]

DoS 攻撃/侵入に対する汎用的な防御手段を提供すること
サーバに対する全ての資源浪費型 DoS 攻撃を完全に防御する汎用手法 は存在し得ないが、被害を最小限することは「あらかじめサービスを隔 離し、サービス間の資源使用の干渉を防ぐこと」で実現可能である。 例えば、同一ホストでウェブサービスとメールサービスを提供しており ウェブサービスに DoS 攻撃を受けた場合、メールサービスに影響を与 えないようにすることを目的としている(ウェブサービスはあきらめる)。 本拡張は、一つのホスト内に複数の隔離環境を提供し、隔離環境を超え た資源のオーバユースおよび各種アクセスを防御する機能を提供する。

本拡張では論理的に各サービスを囲む保護壁を作成し、この保護壁の提 供する資源制御および強制アクセス制御をカーネル内で実現する(図1)。 その際、既存のアプリケーションがそのまま使えるようにする。

本拡張を用いた OS を用いて同一のホスト上で複数のサービスを提供す ることによるメリットは以下の通りである。
  • DoS 攻撃を受けた場合、通常のホストでは操作不能となる場合 でも本拡張を施した OS では制御するための計算機資源を確保する ことができる。そのため、バックアップ系に切り替り、監視し たりするなどの対応が可能となる。
  • 侵入された場合、通常のホストに侵入された場合と比べ、 そのホストに対する破壊活動を小さく抑えられる(カーネルの設 定変更や他の隔離空間のプロセスへのアクセス等はできない)。
また、デメリットとしては以下のものがあげられる。
  • 各隔離環境で使える計算機資源の上限が決められるため、低負 荷時でも計算機資源の利用率は変らない(利用率が固定のため、 過負荷時にはメリットになる)。

[概要]

標準の Linux では隔離環境を提供する機能は chroot(2) 程度しか存在 しないが(注1)、本拡張では、FreeBSD で 実装されている jail (注2)相当の機能を 実装するとともに、jail が未対応の部分にも対応することによって同等 以上の隔離環境(仮想ホスト環境)を提供している。 資源制御については CPU 時間と物理メモリ予約機能を追加し、 システム運用者が各隔離環境が使う計算機資源の上限を設定することが 可能となっている。
つまり、

保護壁 = 隔離環境(仮想ホスト環境) + 計算機資源
となっており、この保護壁はアプリケーションからは通常のホスト(実 際には仮想ホスト)として見える。ただし、仮想ホスト環境と計算機資 源は直交する概念であり、必ず一対一に対応づけなければならないもの ではない。本 OS でも一つの計算機資源に複数の仮想ホスト環境が作る などの柔軟な運用を可能としている。

Protection Barrier

図1. 方式概要

資源予約での制御対象は

  • CPU 時間 (%)
  • 物理メモリ(ページ数)
である。また、強制アクセス制御の対象は
  • socket 通信 (注3)
    (PF_INET:保護壁に割当てられた IP アドレス以外の bind(2)/connect(2)は禁止, INADDR_ANY は自動的に書換え)
    (PF_PACKET:保護壁に割当てられた IP アドレス/ネットマスク でフィルタリング)
  • シグナル
  • SysV IPC (注3)
    (名前/資源空間の分離)
  • ファイルシステム
    (chroot(2)利用)
  • Unix98 PTY
  • Procfs (注3)
    (各種情報のフィルタリング)
  • ...
であり、基本的にシステムコールの処理時に実施される。プロセスは自 分以外のものに対するアクセスは全てシステムコールを利用しなければ ならないため、システムコール処理時に制御することはリーズナブルで ある。 強制アクセス制御のルールは以下のものが自動的に適用される。
  • 保護壁外のプロセス
    • 全てにフルアクセス可能。
    • 利用できる資源は「全資源量から各保護壁が利用してい る総和を差し引いた残り」が全て利用可能。
  • 保護壁内のプロセス
    • 同保護壁内にのみアクセス可能。他の保護壁内および保 護壁外へのアクセスは不可能。
    • 利用できる資源は「保護壁に割当てた量」が利用可能。
これを示したのが図2である。

model

図2. カーネル内論理モデル


[保護壁の生成]

プロセスはサブツリー単位で保護壁にマップ(閉じ込める)され、 さらにマップされたプロセスからforkされた子プロセスは同保護壁 内にマップされる。 また、資源も同様にプロセスサブツリー単位で割当てられる (注4)。

一番最初のプロセスを保護壁にマップするためには新しく追加した システムコール crow_create_reserve(2) および jail(2)を用いる。 crow_create_reserve(2) は当該プロセスおよびそこから fork される全てのプ ロセスの CPU 資源およびメモリ資源の上限をカーネルに要求するシス テムコールであり、jail(2) は当該プロセスおよびそこから fork され る全てのプロセスを保護壁にマップするようカーネルに要求するシス テムコールである。 これらのシステムコールを用いて設定されたパラメータはカーネル内の プロセスの情報を保持するデータから参照可能であり、CPU スケジュー ラ, VM および各システムコール処理ルーチンに使用される。

プロセスは当然自分で前述のシステムコールを使って保護壁を作成す ることができるが、アプリケーションを無改造で使うために、ユーティ リティプログラムを用意している。 ユーティリティプログラムは前述のシステムコールを用いて保護壁を 設定した後、execve(2) を使ってターゲットのプログラムを起動する。

barrier

図3. 保護壁の生成の仕組


[保護壁の破壊]

カーネル内で保持している保護壁の情報はリファレンスカウンタを持っ ており、保護壁に属する全てのプロセスが消滅した時点で自動的に破壊 される。

また、設計方針上、設定した保護壁の設定変更はできない。設定を変更 したい場合には、一度保護壁を破壊してから再度生成するという手順を 踏む必要がある。


[CPU 時間予約を実現する CPU スケジューラ]

(a) 通常の CPU スケジューリング

Linux の CPU スケジューラは schedule() 関数で実現されており、タ イマ割り込みや再スケジュール要求等をトリガに起動される。その際、 プロセスとスレッドの区別はしていない。 schedule() は RUN QUEUE に繋がっている実行可能(RUNNING)な各プロセ スの優先度を goodness() 関数を用いて計算し、その中で一番優先度が 高いプロセスに実行権を渡す(図4参照)。実行可能なプロセスがない場 合には idle タスク (task[0] として常に存在している, 図4の init_task) に実行権を渡す。

goodness()によりプロセスの優先度計算方法は以下の通りである。

goodness()
{
    if (リアルタイムプロセスである) {
        priority += 1000 + プロセスの優先度;/*このプロセスが選択される*/
        return priority;
    }
    if (以前のタイムスライスの残りがある) {
        priority += 残りの ticks;
    }
    if (このプロセスのメモリマップが直前に実行されたプロセスのメモリ
        マップと同じ) {
        priority += 1;
    }
    priority += プロセスの優先度;
    return priority;
}
Normal scheduling

図4. 通常のスケジューリング

(b) CPU 時間予約対応の CPU スケジューリング

それに対して、本拡張での CPU スケジューリングは RUN QUEUE からの プロセスの選び方が異なる。本スケジューラは (*rk_schedule_hook)() == rk_schedule_cpu() で実現されており、 schedule()から呼ばれる。図5にその様子を示す。

CPU予約のパラメータであるパーセンテージは"周期 p[ms]" と"処理時間 c[ms]" に分解される。具体的に周期はデフォルト 1000[ms]とし、処理 時間 c は指定されたパーセンテージと周期を掛け合わせ 100 で除算し たものとする。ただし、小さいタイムスケールでのスケジューリング精 度をあげるため、周期が10[ms](注5)を下回 らない程度に正規化(公約数で除算)する。また、予約していない全ての プロセスは残りのCPU時間を共有する。

例) CPU 30% で予約した場合、

    {p =  1000 [ms], c =  300[ms]} =(正規化)=> {p = 100 [ms], c = 30 [ms]}

が、実際に使われるパラメータとなる。

スケジューラはまず、第一段階として、RUN QUEUE から goodness() を用いて次候補プロセスを選択する。
第二段階として、スケジューラは直前に実行していたプロセスがCPU予 約しているプロセスサブツリーに属している場合、そのプロセスの実行 ticks を計測(注6)し、(CPU予約に対する) 処理時間のアカウンティングを行う。本スケジューラはこの値をCPU予 約に対して実行された時間として扱う。RUN QUEUE にプロセスがある場 合でも、この時間を使いきった CPU 予約を持つプロセス(正確にはプロ セスサブツリーに属するプロセス)には実行権は渡されない。
次に、次候補プロセスがCPU予約しているプロセスサブツリーに属して いる場合、指定された予約パラメータに基づいてハードウェアタイマ をセットし、アカウンティングを開始する。さらに、選択されたプロセ スが、CPU予約を行っていたプロセスサブツリーに属している場合、 そのプロセスサブツリーの中から実行すべきプロセスを選び直す。選び 方は Round Robin アルゴリズムを用い、当該プロセスサブツリー中で RUN QUEUE に入っているものを均等に実行権を渡すよう(実際にはRUN QUEUE のつなぎ直しを行っている)になっている。

CPU Capacity Reservation

図5. CPU時間予約時のスケジューリング

※補足

  • CPU 予約は上限を 80 % とした Admission Control を行ってお り、予約は First Come First Serve(早い者勝ち)で獲得できる。
  • 各プロセスの CPU 予約時間にはそのプロセスが発行したシステ ムコールの処理時間(I/O処理等)は含まれない。


[物理ページ予約のためのページング処理]

Linux ではプロセスに対するメモリ管理は主にページングで実現されて おり、デマンドページング、コピーオンライト、スワップ処理機能が提 供されている。つまり、プロセスが使うデータが物理ページ上に存在す るか、スワップエリアに存在するかはページング処理の結果、言い換え ると犠牲ページの決定アルゴリズムで決まることになる。犠牲ページの 決定アルゴリズムは LRU (Least Recently Used)を採用されている。 Linux 2.2 系カーネルのページング機構は、ページアウト処理が必要に なった際に、使用物理ページ数が多いプロセスを見つけ出し、そのプロ セスの物理ページを LRU (注7)で選択しス ワップアウトする。さらに空きページが必要な場合には同様の処理を必 要な量の空きページができるまで繰り返す。処理内容を以下に示す。

swap_out()
{
    for (counter=nr_tasks/(priority+1); counter; counter--) {
        int max_cnt = 0;
        struct task_struct *pbest;
        for (init 以外の全てのプロセス) {
            if (最近スワップアウトされていない &&
                使用物理ページ数 > max_cnt) {
                max_cnt = 使用物理ページ数;
                pbest = 当該プロセス
            }
        }
    }
    pbest のスワップアウト処理;
}

本拡張では、プロセスサブツリー毎に利用できる物理ページの予約を、 予約されたページ数だけページアウトさせないという処理で実現してい る。具体的には犠牲ページを決める際、あらかじめアカウンティングし てあるプロセスサブツリーが現在利用している物理ページ数と予約され たページ数の差を用いて、ページアウトされるプロセスおよびページ数 を決定する。処理内容は以下の通り。

swap_out()
{
    for (counter=nr_tasks/(priority+1); counter; counter--) {
        int max_cnt = 0, swap_cnt;
        struct task_struct *pbest;
        for (init 以外の全てのプロセス) {
            swap_cnt = 関連する全プロセスの使用物理ページ数 - 予約ページ数;
            if (swap_cnt < 0) swap_cnt = 0;
            if (最近スワップアウトされていない &&
                swap_cnt > max_cnt) {
                max_cnt = swap_cnt;
                pbest = 当該プロセス
            }
        }
    }
    pbest のスワップアウト処理;
}

ここで実現している予約はページ数のみであり、mlock(2)で提供され ているような特定の仮想アドレス空間とマッピングされた物理ページの予 約ではない。この方式を採用した理由は、非リアルタイムアプリケーショ ンには LRU によるページ選択で十分であることと、アプリケーション の改造が不要であることがあげられる。


[仮想ホスト(jail)環境の実現方式]

仮想ホスト(jail)環境は、システムコール処理による強制アクセス制御 によって提供される。

動作の仕組みについて説明する。 まず最初に、最初のプロセスが jail(2) を発行することによっ て、そのプロセスに仮想ホスト(jail)環境の情報がカーネル内のプロセ ス情報に設定することが必要である(図3の手順)。この情報を設定する ことによって、設定以降、そこから fork(2)や execve(2) されるプ ロセスは全て同じ情報を持つことになり、カーネルはその情報を用いて アクセス制御することができる。設定すべきパラメータは以下の通りで ある。

  • 仮想ホスト用 IP アドレス
  • chroot(2) 先のディレクトリ
  • 仮想ホスト用ホスト名
  • 利用したい uid の最小値および最大値
  • 利用したい gid の最小値および最大値
ここで、仮想ホスト(jail)環境の識別子として IP アドレスを用いてい る。

次に、仮想ホスト(jail)環境の情報が設定されたプロセスがシステムコー ルを発行した場合、カーネルはその情報を参照しながらシステムコール 処理を行う。具体的には、カーネルは、システムコールを発行したプロ セス自身の仮想ホスト(jail)環境内のプロセスないしは計算機資源に対 するアクセスである場合には通常の処理を行い、そうでない場合には各 システムコール毎に必要な処理(EPERM を返す, ソケット情報の書き換 えなど)を行う。

次に、本実装で行っている制御について述べる。以下にカーネルの機能 から見た対応のリストの通りである。

  • socket 通信
  • シグナル
  • SysV IPC
  • ファイルシステム
  • Unix98 PTY
  • Procfs
  • その他
これらは実際の処理の内容から、単純なアクセス制御とシステムコール 固有の処理の二つに分類できる。分類と具体的な処理内容を以下に挙げる。

  • 単純なアクセス制御
    • シグナル制御:
      signal(2), waitpid(2) など
    • ネットワーク設定:
      ioctl(2) の設定系
    • カーネルモジュール関連:
      create_module(2), delete_module(2), init_module(2), query_module(2)
    • スペシャルデバイスへのアクセス:
      /dev/* へのシステムコール (但し, /dev/null, /dev/zero, /dev/tty*, /dev/pty* は除く)
    • その他:
      ioperm(2), iopl(2), setpriority(2), stime(2), adjtimex(2), ...

  • システムコール固有の処理
    • socket 通信
      • PF_INET
        保護壁に割当てられた IP アドレス以外の bind(2) / connect(2)は禁止
        127.0.0.1/INADDR_ANY は仮想ホストの IP アドレスに 自動で書換え
      • PF_PACKET
        パケットキャプチャ時に、保護壁に割当てられた IP アドレス/ネットマスクでフィルタリング
    • SysV IPC
      • IPC, Semaphoe, Shared Memory の名前/資源空間を仮想 ホスト用に個々に用意し、そこにのみアクセスできるよ うにする。
    • ファイルシステム
      • chroot(2)を用いた隔離
      • NAMED デバイスのマウント制御(sysctl で許可/不許可 を設定)
    • Procfs
      • マウント
        仮想ホスト内からのマウントは強制的に Read-Only に する。(mount)
      • /proc/<pid>/*
        仮想ホスト内からのアクセス時は、当該仮想ホスト内に 属するプロセスの情報のみ返す。(ls, ps 等)
      • /proc/net/*
        仮想ホスト内からのアクセス時は、当該仮想ホスト内に 関する情報のみ返す。 (ifconfig, netstat -a 等)
    • exec(2), set*id(2)系
      • uid 範囲のチェック
        (sysctl で ON/OFF を設定可能)
        (共有する uid の範囲も sysctl で設定可能)
      • gid 範囲のチェック
        (sysctl で ON/OFF を設定可能)
        (共有する gid の範囲も sysctl で設定可能)
システムコールによる制御を採用した理由は、プロセスは他のプロセス やカーネルにアクセスする時には必ずシステムコールを発行するため、 ここで制御することによって、プロセスからの全ての行動を制御するこ とができることと、それ以外ではオーバヘッドがないことが挙げられる。


[Footnote]

[Reference]
  • Linux/RK: A Portable Resource Kernel in Linux
    • Suichi Oikawa and Ragunathan Rajkumar, CMU, 1998 RTSS Work-in-progress Session
  • Effort toward a Resource kernel --- A Resource Monitor Approach ---
    • Suichi Oikawa, CMU, 1998 RT-Mach Workshop
  • Wrapper型資源予約機構の評価と改良
    • 梶原史雄,盛合敏, NTT, 2000 IPSJ 第61 全国大会
  • Understanding Linux Kernel
    • Daniel P. Bovet and Marco Cesati, O'reilly, 2001
  • UNIX Internals; The New Frontiers
    • Uresh Vahalia, Prentice Hall, 1996
  • Intel Architecture Software Developers Manual Vol1,2,3
    • Intel Corp. 1999

[Acknowledgement]

[Appendix]
/*
 * Functions for read ticks on x86 and PowerPC
 */
#if defined (__i386__)
static inline void
rk_rdtsc(unsigned long long *data_p)
{
        __asm __volatile(
                "rdtsc"
                :"=a"(*(int *)(data_p)), "=d"(*(((int *)data_p)+1)));

}
#elif defined (__powerpc__)
static inline void
rk_rdtsc(unsigned long long *data_p)
{
        asm volatile ("0:mftbu 4;mftb %1;mftbu %0;cmpw 4,%0;bne 0b":"=r" (*(int*)(data_p)),
                 "=r" (*(((int*)data_p)+1)): :"r4");
}
#else
#error No method provided.
#endif