linux:kernel:namespace:user_namespace

文書の過去の版を表示しています。


名前空間の操作,その 6: ユーザ名前空間

進行中の名前空間のシリーズを続けよう.この記事ではユーザ名前空間について細かく見ていく.この機能は大枠は Linux 3.8 で実装が完了した機能である (XFS他の多数のファイルシステムに対する残作業は続いている.後者は 3.9 でマージされた).ユーザ名前空間はユーザとグループ ID の名前空間ごとのマッピングが可能になる.これはユーザ名前空間内と名前空間外で異なるプロセスとグループの ID を持つ事が可能になるということである.中でも注目すべきは,プロセスは名前空間外では 0 でない ID を持ち,同時に名前空間内では 0 のユーザ ID を持つ事ができることである.言い換えると,プロセスはユーザ名前空間外では特権を持たない操作だが,名前空間内では root 特権を持っているという事である.

ユーザ名前空間は clone() や unshare() を呼ぶ時に CLONE_NEWUSER フラグを指定することで作成する.(他のタイプの名前空間を作る時に使うフラグと違って) Linux 3.8 から使え,ユーザ名前空間を作成するのに特権は不要である.以下で挙げる例では,ユーザ名前空間の全ては ID 1000 の非特権ユーザを使って作られている.

ユーザ名前空間の調査を始めるために,小さなプログラム demo_userns.cを使う.これは新しいユーザ名前空間内で子プロセスを作成する.子プロセスは単純に実効ユーザとグループ ID をケーパビリティと同時に表示するだけのものである.非特権ユーザでこのプログラムを実効すると以下のような結果が得られる.

    $ id -u          # Display effective user ID of shell process
    1000
    $ id -g          # Effective group ID of shell
    1000
    $ ./demo_userns 
    eUID = 65534;  eGID = 65534;  capabilities: =ep

このプログラムからの出力には面白いことがいくつかある.その一つは,子プロセスに割り当てられるケーパビリティである.“=ep” という文字列 (cap_to_text() 関数がケーパビリティをテキスト表示で表示) は,子プロセスで有効で許可されたフルセットのケーパビリティを持つことを示している.このプログラムが非特権アカウントで実効したにも関わらずである.ユーザ名前空間が作られると,名前空間の最初のプロセスは名前空間内でのフルセットのケーパビリティを与えられる.これによりプロセスは,名前空間内で他のプロセスが作成される前に,名前空間内での必要な初期化が可能になる.

2 つ目は,子プロセスのユーザとグループの ID である.先に述べたように,プロセスのユーザとグループの ID はユーザ名前空間の内外で異なる可能性がある.しかし,名前空間内のユーザ ID とそれに一致する名前空間外のユーザ ID のマッピングが必要である.これはグループ ID も同様である.これにより,システムはユーザ名前空間内のプロセスがシステム全体に影響を及ぼすような操作を行う時に,適切なパーミッションのチェックをすることができる.(例えば,名前空間外のプロセスへシグナルを送るとかファイルにアクセスするとか)

getuid(), getgid() のようなプロセスのユーザ,グループ ID を返すシステムコールは常に,呼び出したプロセスが属するユーザ名前空間内で見える通りのクレデンシャルを返す.もしユーザ ID が名前空間内でのマッピングを持たない場合,ユーザ ID を返すシステムコールは /proc/sys/kernel/overflowuid で定義された値を返す.これは通常のデフォルトは 65534 である.はじめは,ユーザ名前空間はマッピングを持たない.なので,名前空間内の全てのユーザ ID はこの値にマップされる.同様に新しい名前空間はグループ ID のマッピングも持たない.全てのマッピングされないグループ ID は/proc/sys/kernel/overflowgid にマップされる (デフォルト値は overflowuid と同じ)

上記の出力から得られない他の注目に値する重要な点がひとつある.新しいプロセスは新しいユーザ名前空間内でフルセットのケーパビリティを持つが,親の名前空間では特権を持たないことである.これは clone() を呼んだプロセスのクレデンシャルとケーパビリティに関わらずである.特に,root が clone(CLONE_NEWUSER) を使った時でさえ,結果として出来る子プロセスは親の名前空間ではケーパビリティがない.

ユーザ名前空間の作成に関する最後の興味深い点は,名前空間はネスト可能であるということである.これは,初期のユーザ名前空間以外のユーザ名前空間のそれぞれは,親のユーザな前空間を持つという事であり,子のユーザ名前空間と持たないか,1 つ以上の子ユーザ名前空間を持つという事である.CLONE_NEWUSER フラグを使って clone() または unshare() でユーザ名前空間を作成するプロセスが属するユーザ名前空間がユーザ名前空間の親となる.ユーザ名前空間の親子関係の重要性は,この記事の後の部分で明らかになる.

通常,新しいユーザ名前空間を作った後の最初の段階の作業の一つに,名前空間内で作られるプロセスのユーザ,グループ ID が使うマッピングの定義がある.この作業は,ユーザ名前空間内のプロセスの 1 つに対する /proc/PID/uid_map, /proc/PID/gid_map ファイルにマッピング情報を書き込む事である (初期状態はこの 2 つのファイルは空である).この情報は 1 行以上であり,スペース区切りの 3 つの値が含まれる.

    ID-inside-ns   ID-outside-ns   length

ID-inside-ns と length が名前空間内の ID の範囲を定義する.この範囲が名前空間外の同じ長さの範囲の ID にマップされる.ID-outside-ns には外側での範囲の開始番号を指定する./proc/PID/uid_map (または /proc/PID/gid_map) ファイルを open するあるプロセスが,PID を持つ別のプロセスと同じ名前空間内にいるかどうかによって,どのように変換されるか.

  • もし 2 つのプロセスが同じ名前空間に存在する場合,ID-outside-ns は PID を持つプロセスの親の名前空間内のユーザ ID (グループ ID) に変換される.ここで共通の事は,プロセスは自身の持つマッピングファイルに書き込むということである.
  • もし 2 つのプロセスが異なる名前空間に存在する場合,ID-outside-ns は /proc/PID/uid_map (/proc/PID/gid_map) を開いたプロセスのユーザ名前空間内のユーザ ID (グループID) に変換される.(マッピングを) 書き込むプロセスは,その時に自身のユーザ名前空間に対するマッピングを定義することになる.

1 つ以上の demo_userns プログラムを起動したと仮定しよう.しかし,この時,単一のコマンドライン行引数 (任意の文字列) を持つと仮定しよう.この時プログラムは,数秒毎にクレデンシャルとケーパビリティを表示し続けながらループする.

    $ ./demo_userns x
    eUID = 65534;  eGID = 65534;  capabilities: =ep
    eUID = 65534;  eGID = 65534;  capabilities: =ep

ここで,別のターミナルウィンドウにスイッチしてみよう.別の名前空間でシェルプロセスを実行するためである (つまり,demo_userns を実行しているプロセスの親の名前空間ということである).そして demo_userns が作った新しい名前空間内の子プロセスに対するユーザ ID のマッピングを作成する.

    $ ps -C demo_userns -o 'pid uid comm'      # Determine PID of clone child
      PID   UID COMMAND 
     4712  1000 demo_userns                    # This is the parent
     4713  1000 demo_userns                    # Child in a new user namespace
    $ echo '0 1000 1' > /proc/4713/uid_map

もし,demo_userns が実行されているウィンドウの表示があった場合,こんな出力になる.

    eUID = 0;  eGID = 65534;  capabilities: =ep

言い換えると,親のユーザ名前空間でユーザ ID 1000 (元は 65534 にマップされていた) は,demo_userns が作ったユーザ名前空間ではユーザ ID 0 にマップされている.この点から,このユーザ ID を使う新しい名前空間内での全ての操作は番号 0 が見えることになるだろう.一方で親のユーザ名前空間内でのこれに一致する操作はユーザ ID 1000 を持つプロセスと同じように見えるだろう.

同様にして新しいユーザ名前空間内でのグループ ID のマッピングを作る事も可能である.他のターミナルウィンドウに移動して,親の名前空間内でグループ ID 1000 に対するユーザ名前空間内でのグループ ID 0 の単一のマッピングを作成する.

    $ echo '0 1000 1' > /proc/4713/gid_map

demo_userns を実行しているウィンドウに戻ってみると,有効なグループ ID に表示が反映されているのが見える.

    eUID = 0;  eGID = 0;  capabilities: =ep
  • linux/kernel/namespace/user_namespace.1377957586.txt.gz
  • 最終更新: 2013/08/31 13:59
  • by tenforward