文書の過去の版を表示しています。
名前空間の操作,その 2: 名前空間 API
名前空間はグローバルなシステムリソースを抽象的なものの中に包みこむ.隔離されたリソースのインスタンスを持つ名前空間内にプロセスを出現させるということである.名前空間は様々な目的に使われる.最も有名なのは軽量の仮想化テクニックであるコンテナの実装である.これは名前空間と名前空間 API の詳細を見ていく記事のシリーズの第 2 部である.この記事では名前空間 API を少し詳細に見ていく.そして多数のサンプルプログラムで実際の API を説明する.
名前空間の API は 3 つのシステムコール,clone(), unshare(), setns() と多数の /proc 以下のファイルから構成される.この記事では,この全てのシステムコールと /proc 以下のファイルをいくつか見ていく.操作するのがどのタイプの名前空間なのかを特定するために,3 つのシステムコールは CLONE_NEW* という定数を仕様する.先の記事で挙られていた CLONE_NEWIPC, CLONE_NEWNS, CLONE_NEWNET, CLONE_NEWPID, CLONE_NEWUSER, CLONE_NEWUTS である.
新しい名前空間内で子プロセスを生成する: clone()
名前空間を作成する方法の 1 つが,新しいプロセスを生成するシステムコールである clone() を使うことによるものである.clone() は以下のようなプロトタイプを持つ.
int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);
基本的にclone() は,伝統的な UNIX の fork() システムコールのより一般的なバージョジョンである.その機能は flags により制御される.clone() の様々な操作をコントロールするための,全部で 20 以上の異なる CLONE_* フラグが存在する.そのフラグは仮想メモリやオープンするファイルディスクリプタやシグナル操作のようなリソースを親子で共有するかどうかのためのものを含む.呼び出しで CLONE_NEW* の一つが指定された場合,指定したタイプに一致する新しい名前空間が作成され,新しいプロセスがその名前空間のメンバーとして作成される.flags には複数の CLONE_NEW* ビットを指定することも可能である.
我々のサンプルプログラム (demo_uts_namespace.c) は UTS 名前空間を作るために CLONE_NEWUTS を指定して clone() を使っている.先週見たように,UTS 名前空間は 2 つのシステムの識別子であるホスト名と NIS ドメイン名を隔離する.これは sethostname() と setdomainname() を使って設定され,uname() システムコールを使って返される.全ソースコードはここにある.以下で,このプログラムのキーとなるいくつかの部分にフォーカスを当てる (簡潔な記述のために,サンプルの全コードには載っているエラーチェックを省いている).
サンプルプログラムはコマンドラインオプションを 1 つ与える.実行すると,新しい UTS 名前空間で動く子プロセスを 1 つ作成する.名前空間内で,子プロセスはコマンドラインオプションで与えた文字列でホスト名を変更する.
メインプログラムの最初の重要な部分は子プロセスを作成する clone() コールである:
child_pid = clone(childFunc, child_stack + STACK_SIZE, /* Points to start of downwardly growing stack */ CLONE_NEWUTS | SIGCHLD, argv[1]); printf("PID of child created by clone() is %ld\n", (long) child_pid);
新しい子供はユーザ定義の関数 childFunc(); 内で実行される.この関数は引数として clone() の最後の引数 argv[1] を受け取る.flags 引数として CLONE_NEWUTS が指定されているので,子供は新たに UTS 名前空間を作成して実行される.
メインプログラムはそれから一瞬 sleep する.これは,子供に自身の UTS 名前空間でホスト名を変える時間を与える (雑な) 方法である.プログラムはそれから uname() を使って親の UTS 名前空間でホスト名を取得し,ホスト名を表示する
sleep(1); /* Give child time to change its hostname */ uname(&uts); printf("uts.nodename in parent: %s\n", uts.nodename);
その間に childFunc() 関数は clone() によって作られた子供で実行され,最初に引数で与えられた値でホスト名を変更し,それを取得し,表示する.
sethostname(arg, strlen(arg); uname(&uts); printf("uts.nodename in child: %s\n", uts.nodename);
終了前に,子供はしばらくの間 sleep する.これは子供の UTS 名前空間が保持されたままにする効果がある.この間に,後で述べる実験をいくつか実行する.
プログラムを実行すると,親と子のプロセスが独立した UTS 名前空間を持つ事を実演する.
$ su # Need privilege to create a UTS namespace Password: # uname -n antero # ./demo_uts_namespaces bizarro PID of child created by clone() is 27514 uts.nodename in child: bizarro uts.nodename in parent: antero
他のほとんどの名前空間と同様に,UTS 名前空間の作成には特権が必要である (はっきり言うと CAP_SYS_ADMIN).これは,set-user-ID アプリケーションが不適切なことを偽って行い,期待しないホスト名を持つようなシナリオを防ぐのに必要である.
他の可能性として,set-user-ID アプリケーションがロックファイルの一部としてホスト名を使うかもしれないことがある.もし特権を持たないユーザが UTS 名前空間で任意のホスト名を設定してアプリケーションを実行できたら,アプリケーションに色々な攻撃の可能性を与えることになるだろう.このことにより,非常に簡単にロックファイルの効果を消し,異なる UTS 名前空間で実行するアプリケーションのインスタンスの不正行為のトリガとなるだろう.加えて,悪意あるユーザが UTS 名前空間で set-user-ID アプリケーションをホスト名を設定して実行し,重要なファイルを上書きするロックファイルを生成するということも引き起こせる.(ホスト名はスラッシュを含む任意の文字を含むことができる)
/proc/PID/ns ファイル
プロセスごとにそれぞれの名前空間に対して 1 つのファイルを含む /proc/PID/ns ディレクトリが存在する.Linux 3.8 以降,これらのファイルはぞれぞれ特殊なシンボリックリンクファイルになっている.これはプロセスに紐づく名前空間に対する操作を行うためのある種のハンドルとして用意されている.
$ ls -l /proc/$$/ns # $$ is replaced by shell's PID total 0 lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 ipc -> ipc:[4026531839] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 mnt -> mnt:[4026531840] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 net -> net:[4026531956] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 pid -> pid:[4026531836] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 user -> user:[4026531837] lrwxrwxrwx. 1 mtk mtk 0 Jan 8 04:12 uts -> uts:[4026531838]
このシンボリックリンクの使い方の一つに,2 つのプロセスが同じ名前空間に属しているかどうかを判別する事がある.カーネルは,もし 2 つのプロセスが同じ名前空間に属している場合は,/proc/PID/ns 内の一致するシンボリックリンクに対して同じ inode 番号を返す事を保証する.inode 番号は stat() システムコール (内の st_ino フィールド) を使って取得できる.カーネルは /proc/PID/ns シンボリックリンクのそれぞれが,inode 番号が後に続く名前空間の名前を指すような文字列からなる名前を指し示すようにも構成します.ls -l コマのか readlink コマンドを使って名前を調べることができます.