Provided by: manpages-ja_0.5.0.0.20221215+dfsg-1_all bug

名前

       user_namespaces - Linux ユーザー名前空間の概要

説明

       名前空間の概要については namespaces(7) を参照。

       ユーザー名前空間は、  セキュリティに関連する識別子や属性、  特にユーザー  ID やグループ ID
       (credentials(7) 参照)、 root ディレクトリ、 キー (keyctl(2) 参照)、 ケーパビリティを分離す
       る。  プロセスのユーザー  ID  とグループ ID はユーザー名前空間の内部と外部で異なる場合があ
       る。 特に、 あるプロセスがユーザー名前空間の外部では通常の非特権ユーザー ID を持つが、  同
       時にユーザー名前空間の内部ではユーザー ID 0 を持つという場合がある。 言い換えると、 そのプ
       ロセスはそのユーザー名前空間の内部での操作に対してすべての特権を持つが、  名前空間の外部で
       の操作では特権を持たない。

   ネストされた名前空間、名前空間のメンバー
       ユーザー名前空間は入れ子にすることができる。 つまり、 最初の ("root") 名前空間以外の各名前
       空間は親のユーザー名前空間を持ち、 0 個以上のユーザー名前空間を持つということである。 親の
       ユーザー名前空間は、  CLONE_NEWUSER  フラグを指定して  unshare(2) や clone(2) を呼び出して
       ユーザー名前空間を作成したプロセスのユーザー名前空間である。

       カーネルにより (バージョン 3.11 以降では) ユーザー名前空間のネスト数に 32 という上限が課さ
       れる。 unshare(2) や clone(2) の呼び出しでこの上限を超えてしまう場合はエラー EUSERS で失敗
       する。

       各プロセスは必ず 1 個のユーザー名前空間のメンバーとなる。 CLONE_NEWUSER フラグを指定せずに
       fork(2)  や clone(2) でプロセスを作成した場合、 そのプロセスは親プロセスと同じユーザー名前
       空間のメンバーとなる。     シングルスレッドのプログラムは、     変更先のユーザー名前空間で
       CAP_SYS_ADMIN  を持っていれば、  setns(2) を使って別のユーザー名前空間に参加することができ
       る。 変更時に、 変更後の名前空間ですべてのケーパビリティを獲得する。

       CLONE_NEWUSER を指定して clone(2) や unshare(2) を呼び出すと、 新しいプロセス (clone(2) の
       場合)  や呼び出したプロセス (unshare(2) の場合) がその呼び出しで作成された新しいユーザー名
       前空間のメンバーとなる。

   ケーパビリティ
       CLONE_NEWUSER フラグが指定された clone(2) で作成された子プロセスは、  新しい名前空間ですべ
       てのケーパビリティを持った状態で開始される。  同様に、 unshare(2) を使って新しいユーザー名
       前空間を作成したり、  setns(2)  を使って既存のユーザー名前空間に参加したりしたプロセスは、
       その名前空間ですべてのケーパビリティを獲得する。 一方、 そのプロセスは、親のユーザー名前空
       間 (clone(2) の場合) や直前のユーザー名前空間 (unshare(2) や setns(2) の場合) では、  root
       ユーザー  (root 名前空間のユーザー ID 0 のプロセス) により新しい名前空間の作成や参加が行わ
       れた場合であっても、 ケーパビリティを全く持たない。

       execve(2) の呼び出しでは、 プロセスのケーパビリティは通常の方法 (capabilities(7) 参照)  で
       再計算され、 通常は、 名前空間内でユーザー ID 0 を持つ場合や実行ファイルが空でない継承可能
       ケーパビリティマスクを持っている場合を除くと、 すべてのケーパビリティを失うことになる。 下
       記の、ユーザー ID やグループ ID のマッピングの議論を参照。

       CLONE_NEWUSER  フラグを使って  clone(2),  unshare(2),  setns(2)  を呼び出すと、 子プロセス
       (clone(2) の場合) や呼び出し元 (unshare(2) や setns(2) の場合)  では  "securebits"  フラグ
       (capabilities(7) 参照) がデフォルト値に設定される。 呼び出し元は setns(2) の呼び出し後は元
       のユーザー名前空間ではケーパビリティを持たないので、 setns(2)  を  2  回呼び出して一度別の
       ユーザー名前空間に移動して元のユーザー名前空間に戻ることで、  プロセスが元のユーザー名前空
       間にとどまりつつ自身の "securebits" フラグを再設定することはできない。

       ユーザー名前空間内部でケーパビリティを持つというのは、  そのプロセスがその名前空間の支配下
       にあるリソースに対してのみ  (特権を必要とする) 操作を実行できるということである。 プロセス
       が特定のユーザー名前空間でケーパビリティを持つかどうかを判定するルールは以下の通りである。

       1. プロセスがその名前空間のメンバーで、実効ケーパビリティセットにそのケーパビリティがあれ
          ば、  そのプロセスはユーザー名前空間内でケーパビリティを持つ。 プロセスが実効ケーパビリ
          ティセットでケーパビリティを得るにはいくつかの方法がある。 例えば、 set-user-ID  プログ
          ラムや関連するファイルケーパビリティを持った実行ファイルを実行する。  また、 すでに説明
          したとおり、 プロセスは clone(2), unshare(2), setns(2) の結果としてケーパビリティを獲得
          することもできる。

       2. プロセスがユーザー名前空間でケーパビリティを持っている場合、 そのプロセスはすべての子供
          の名前空間 (および削除された子孫の名前空間) でケーパビリティを持つ。

       3. ユーザー名前空間が作成された際、 カーネルはその名前空間の「所有者」として作成したプロセ
          スの実効ユーザー  ID を記録する。 親のユーザー名前空間に属するプロセスで、 そのプロセス
          の実効ユーザー ID が名前空間の所有者と一致する場合、 そのプロセスはその名前空間ですべて
          のケーパビリティを持つ。  一つ前のルールも合わせて考えると、 このプロセスはすべての削除
          された子孫のユーザー名前空間ですべてのケーパビリティを持つことを意味する。

   ユーザー名前空間と他の名前空間の関係
       Linux 3.8 以降では、 非特権プロセスがユーザー名前空間を作成することができる。 また、  呼び
       出し元のユーザー名前空間で CAP_SYS_ADMIN ケーパビリティを持っているだけで、 マウント名前空
       間、 PID 名前空間、 IPC 名前空間、 ネットワーク名前空間、 UTS 名前空間を作成できる。

       ユーザー名前空間以外の名前空間が作成された場合、  その名前空間は呼び出したプロセスが名前空
       間の作成時にメンバーであったユーザー名前空間により所有される。  ユーザー名前空間以外の名前
       空間における操作には、 対応するユーザー名前空間でのケーパビリティが必要である。

       一つの clone(2) や unshare(2) の呼び出しで CLONE_NEWUSER が他の CLONE_NEW*  フラグと一緒に
       指定された場合、     そのユーザー名前空間が最初に作成されることが保証され、     子プロセス
       (clone(2) の場合) や呼び出し元 (unshare(2) の場合)  はその呼び出しで作成される残りの名前空
       間で特権を持つ。 したがって、 特権を持たない呼び出し元がフラグを組み合わせて指定することが
       できる。

       新しい IPC 名前空間、 マウント名前空間、 ネットワーク名前空間、 PID 名前空間、 UTS  名前空
       間が  clone(2) や unshare(2) で作成される際、 カーネルは新しい名前空間に対して作成したプロ
       セスのユーザー名前空間を記録する (この関連付けは変更できない)。 その新しい名前空間のプロセ
       スがその後名前空間で分離されたグローバルリソースに対して特権操作を行う場合、  カーネルが新
       しい名前空間に対して関連付けたユーザー名前空間でのプロセスのケーパビリティに基づいてアクセ
       ス許可のチェックが行われる。

   マウント名前空間における制限
       マウント名前空間に関しては以下の点に注意すること。

       *  マウント名前空間は所有者のユーザー名前空間を持つ。 所有者のユーザー名前空間が親のマウン
          ト名前空間の所有者のユーザー名前空間と異なるマウント名前空間は、 特権が少ないマウント名
          前空間 (less privileged mount namespace) である。

       *  特権が少ないマウント名前空間を作成する場合、  共有マウントは  slave  マウントに縮小され
          る。 これにより、 特権の少ないマウント名前空間で実行されるマッピングが、 より特権を持つ
          マウント名前空間 (more privileged mount namespace) に伝搬しないことが保証される。

       *  より特権を持つマウントで一つのまとまりとして行われたマウントは一つにまとまったままとな
          り、 特権が少ないマウント名前空間で分割することはできない。 (unshare(2) の  CLONE_NEWNS
          操作では、  元のマウント名前空間のすべてのマウントは一つのまとまりとして扱われ、 マウン
          ト名前空間間で伝わる再帰的なマウントでは一つのまとまりとして伝わる。)

       *  より特権を持つマウント名前空間から特権の少ないマウント名前空間に伝わる際に、   mount(2)
          の  MS_RDONLY, MS_NOSUID, MS_NOEXEC フラグと "atime" フラグ (MS_NOATIME, MS_NODIRATIME,
          MS_REALTIME)  設定はロックされ、   特権の少ないマウント名前空間では変更することはできな
          い。

       *  ある名前空間でマウントポイントとなっているが別の名前空間でのマウントポイントになってい
          ないファイルやディレクトリは、 マウントポイントになっていないマウント名前空間では (通常
          のアクセス許可チェックにもとづいて)  rename, unlink, remove (rmdir(2)) を行うことができ
          る。

          以前は、     別のマウント名前空間でマウントポイントとなっていたファイルやディレクトリを
          rename, unlink, remove しようとすると、 エラー EBUSY が返されていた。 この動作は、 (NFS
          などで) 適用にあたっての技術的な問題があるとともに、 より特権を持つユーザーに対してサー
          ビス不能攻撃  (denial-of-service attack) を許してしまっていた (ファイルをバインドマウン
          トで更新することができなくなっていた)。

   ユーザー ID とグループ ID のマッピング: uid_map  gid_map
       ユーザー名前空間が作成された際、 その名前空間は親のユーザー名前空間へのユーザー  ID  (とグ
       ループ    ID)    のマッピングを行わずに開始される。    ファイル   /proc/[pid]/uid_map/proc/[pid]/gid_map (Linux 3.5 以降で利用可能) でプロセス pid のユーザー名前空間内でのユー
       ザー  ID とグループ ID のマッピングにアクセスできる。 これらのファイルを読み出してユーザー
       名前空間内のマッピングを参照したり、  これらのファイルに書き込んでマッピングを  (一度だけ)
       定義することができる。

       以下の段落で  uid_map の詳細を説明する。 gid_map に関しても全く同じである。 "user ID" とい
       う部分を "group ID" に置き換えればよい。

       uid_map ファイルで、 プロセス pid のユーザー名前空間から uid_map  をオープンしたプロセスの
       ユーザー名前空間にユーザー ID のマッピングが公開される (公開するポリシーの条件については下
       記を参照)。 言い換えると、 別のユーザー名前空間のプロセスでは、 特定の uid_map  ファイルを
       読み出した際に潜在的には別の値が見えることがあるということである。  見える値は読み出したプ
       ロセスのユーザー名前空間のユーザー ID マッピングに依存する。

       uid_map ファイルの各行は 2 つのユーザー名前空間間の連続するユーザー ID  の範囲の  1  対  1
       マッピングを指定する (ユーザー名前空間が最初に作成された際にはこのファイルは空である)。 各
       行の指定の形式はホワイトスペース区切りの 3 つの数字である。 最初の  2  つの数字は  2  つの
       ユーザー名前空間それぞれの開始ユーザー  ID を指定する。 3 つ目の数字はマッピングされる範囲
       の長さを指定する。 詳しくは、各フィールドは以下のように解釈される。

       (1) プロセス pid のユーザー名前空間におけるユーザー ID の範囲の開始値。

       (2) 1 番目のフィールドで指定されたユーザー ID がマッピングされる先のユーザー ID の範囲の開
           始値。 2 番目のフィールドがどのように解釈されるかは、 uid_map をオープンしたプロセスと
           プロセス pid が同じユーザー名前空間かどうかに依存する。 以下のとおり。

           a) 2 つのプロセスが異なるユーザー名前空間に属す場合、 2 番目のフィールドは uid_map  を
              オープンしたプロセスのユーザー名前空間におけるユーザー ID の範囲の開始値である。

           b) 2  つのプロセスが同じユーザー名前空間に属す場合、  2 番目のフィールドはプロセス pid
              の親のユーザー名前空間におけるユーザー ID の範囲の開始値である。 この場合、 uid_map
              をオープンしたプロセス  (よくあるのは  /proc/self/uid_map をオープンした場合である)
              は、  このユーザー名前空間を作成したプロセスのユーザー名前空間に対するユーザー   ID
              マッピングを参照することができる。

       (3) 2 つのユーザー名前空間間でマッピングされるユーザー ID の範囲の長さ。

       ユーザー  ID (グループ ID) を返すシステムコール、例えば getuid(2), getgid(2) や stat(2) が
       返す構造体の  credential  フィールド、は呼び出し元のユーザー名前空間にマッピングされたユー
       ザー ID (グループ ID) を返す。

       プロセスがファイルにアクセスする場合、  アクセス許可のチェックやファイル作成時の ID 割り当
       てのために、 そのユーザー ID とグループ ID は初期ユーザー名前空間にマッピングされる。 プロ
       セスが stat(2) を使ってファイルのユーザー ID やグループ ID を取得する際には、 上記の反対方
       向に ID のマッピングが行われ、 プロセスにおける相対的なユーザー ID とグループ ID  の値が生
       成される。

       初期ユーザー名前空間は親の名前空間を持たないが、 一貫性を持たせるため、 カーネルは初期の名
       前空間に対してダミーのユーザー ID とグループ ID のマッピングを提供する。  初期の名前空間の
       シェルから uid_map ファイル (gid_map も同じ) を参照するには以下のようにする。

           $ cat /proc/$$/uid_map
                    0          0 4294967295

       このマッピングは、  この名前空間のユーザー ID 0 から始まる範囲が (実際には存在しない) 親の
       名前空間の 0 から始まる範囲にマッピングされ、 範囲の流さは 32 ビットの unsigned integer の
       最大値である、  と言っている。 (ここで 4294967295 (32 ビットの符号付き -1 の値) は意図的に
       マッピングされていない。 (uid_t) -1 は (setreuid(2) など) いくつかのインターフェースで "no
       user  ID" (ユーザー ID なし) を示す手段として使用されているので、 意図的にこのようになって
       いる。 (uid_t) -1 をマッピングせず、 利用できないようにすることで、 これらのインターフェー
       スを使った際に混乱が起こらないように保証している。)

   ユーザー ID とグループ ID のマッピングの定義: uid_map  gid_map への書き込み
       新しいユーザー名前空間を作成した後、  新しいユーザー名前空間におけるユーザー ID のマッピン
       グを定義するため、 その名前空間のプロセスの「一つ」の uid_map ファイルに「一度だけ」書き込
       みを行うことができる。 ユーザー名前空間の uid_map ファイルに二度目以降の書き込みを行おうと
       すると、 エラー EPERM で失敗する。 gid_map ファイルについては同じルールが適用される。

       uid_map (gid_map) に書き込む行は以下のルールに従っていなければならない。

       *  3 のフィールドは有効な数字でなければならず、最後のフィールドは 0 より大きくなければなら
          ない。

       *  行は改行文字で終了しなければならない。

       *  ファイルの行数には上限がある。  Linux 3.8 時点では、上限は 5 行である。 さらに、 ファイ
          ルに書き込むバイト数はシステムページサイズより小さくなければならず、 書き込みはファイル
          の先頭に対して行わなければならない  (つまり、  lseek(2)  や pwrite(2) を使って 0 以外の
          ファイルオフセットに書き込むことはできない)。

       *  各行で指定されるユーザー ID (グループ ID)  の範囲は他の行が指定する範囲と重なってはなら
          ない。  最初の実装  (Linux 3.8) では、 この要件は、 後続行のフィールド 1 とフィールド 2
          の両方の値が昇順になっていなければならないという追加の要件を設け、 これが満たされなかっ
          た場合は有効なマッピングは作成されない、  という単純な実装により満たされていた。  Linux
          3.9 以降ではこの制限は修正され、 重複がない有効なマッピングであればどんな組み合わせでも
          指定できるようになった。

       *  少なくとも 1 行はファイルに書き込まなければならない。

       上記のルールを満たさない書き込みはエラー EINVAL で失敗する。

       プロセスが /proc/[pid]/uid_map (/proc/[pid]/gid_map) ファイルに書き込むためには、 以下の要
       件がすべて満たされる必要がある。

       1. 書き込みプロセスは、 プロセス pid のユーザー名前空間で CAP_SETUID (CAP_SETGID) ケーパビ
          リティを持っていなければならない。

       2. 書き込みプロセスは、 プロセス pid のユーザー名前空間もしくはプロセス pid の親のユーザー
          名前空間に属していなければならない。

       3. マッピングされたユーザー ID (グループ ID)  は親のユーザー名前空間にマッピングを持ってい
          なければならない。

       4. 以下のいずれか一つが真である。

          *  uid_map  (gid_map) に書き込まれるデータは、 書き込みを行うプロセスの親のユーザー名前
             空間でのファイルシステムユーザー ID (グループ ID) をそのユーザー名前空間でのユーザー
             ID (グループ ID) にマッピングする 1 行で構成されている。

          *  オープンしたプロセスが親のユーザー名前空間で  CAP_SETUID (CAP_SETGID) ケーパビリティ
             を持っている。  したがって、  特権プロセスは親のユーザー名前空間の任意のユーザー  ID
             (グループ ID) に対するマッピングを作成できる。

       上記のルールを満たさない書き込みはエラー EPERM で失敗する。

   マッピングされていないユーザー ID とグループ ID
       マッピングされていないユーザー ID (グループ ID) がユーザー空間に公開される場合はいろいろあ
       る。 例えば、 新しいユーザー名前空間の最初のプロセスが、  その名前空間に対するユーザー  ID
       マッピングが定義される前に  getuid()  を呼び出すなどである。  このようなほとんどの場合で、
       マッピングされていないユーザー ID はオーバーフローユーザー ID (グループ  ID)に変換される。
       デフォルトのオーバーフローユーザー   ID   (グループ  ID)  は  65534  である。  proc(5)  の
       /proc/sys/kernel/overflowuid/proc/sys/kernel/overflowgid の説明を参照。

       マッピングされていない ID がこのようにマッピングされる場合としては、 ユーザー ID  を返すシ
       ステムコール  (getuid(2),  getgid(2) やその同類)、 UNIX ドメインソケットで渡される ID 情報
       (credential)、 stat(2) が返す ID 情報、 waitid(2)、 System V  IPC  "ctl"  IPC_STAT  操作、
       /proc/PID/status/proc/sysvipc/*  内のファイルで公開される ID 情報、 シグナル受信時の
       siginfo_tsi_uid フィールドで返される ID 情報  (sigaction(2)  参照)、  プロセスアカウン
       ティングファイルに書き込まれる ID 情報 (acct(5) 参照)、 POSIX メッセージキュー通知で返され
       る ID 情報 (mq_notify(3) 参照) がある。

       マッピングされていないユーザー ID やグループ ID が対応するオーバーフロー ID 値に変換され「
       ない」重要な場合が一つある。  2 番目のフィールドにマッピングがない uid_mapgid_map ファ
       イルを参照した際、 そのフィールドは 4294967295 (unsigned integer では -1) が表示される。

   set-user-ID  set-group-ID されたプログラム
       ユーザー名前空間内のプロセスが set-user-ID (set-group-ID) されたプログラムを実行した場合、
       そのプロセスの名前空間内の実効ユーザー ID (実効グループ ID) は、 そのファイルのユーザー ID
       (グループ ID) にマッピングされる。 しかし、 そのファイルのユーザー ID 「か」グループ ID が
       名前空間内のマッピングにない場合、  set-user-ID  (set-group-ID) ビットは黙って無視される。
       新しいプログラムは実行されるが、 そのプロセスの実効ユーザー ID (実効グループ ID)  は変更さ
       れないままとなる。  (これは  MS_NOSUID  フラグ付きでマウントされたファイルシステム上にある
       set-user-ID/set-group-ID プログラムを実行した場合の動作を反映したものである。 mount(2)  を
       参照。)

   その他
       プロセスのユーザー ID とグループ ID が UNIX ドメインソケットを通して別のユーザー名前空間の
       プロセスに渡された場合 (unix(7) の SCM_CREDENTIALS の説明を参照)、 ユーザー ID  とグループ
       ID は受信プロセスのユーザー ID とグループ ID のマッピングに基づき対応する値に翻訳される。

準拠

       名前空間は Linux 独自の機能である。

注意

       長年にわたり、Linux   カーネルには特権ユーザーに対してだけ利用できる機能が多く追加されて来
       た。 これは set-user-ID-root  アプリケーションを混乱させる潜在的な可能性を考慮してである。
       一般的には、  ユーザー名前空間の root ユーザーにだけこれらの機能の使用を許可するのが安全で
       ある。 なぜなら、ユーザー名前空間の中にいる間は、 ユーザー名前空間の root ユーザーが持って
       いる以上の特権を得ることはできないからである。

   可用性
       ユーザー名前空間を使用するには、 CONFIG_USER_NS オプションが有効になったカーネルが必要であ
       る。 ユーザー名前空間をカーネルの様々なサブシステムのサポートを必要とする。 サポートされて
       いないサブシステムがカーネルに組み込まれている場合、  ユーザー名前空間のサポートを有効にす
       ることはできない。

       Linux 3.8 時点では、 ほとんどの関連するサブシステムはユーザー名前空間に対応しているが、 多
       くのファイルシステムにユーザー名前空間間でユーザー ID やグループ ID のマッピングを行うのに
       必要な基盤がなかった。 Linux 3.9 では、 残りの未サポートのファイルシステムの多くで必要な基
       盤のサポートが追加された  (Plan  9  (9P), Andrew File System (AFS), Ceph, CIFS, CODA, NFS,
       OCFS2)。 Linux 3.11 では、最後の主要な未サポートのファイルシステムであった XFS  のサポート
       が追加された。

       以下のプログラムは、ユーザー名前空間で実験を行えるように設計されている。  他の種類の名前空
       間も扱える。  このプログラムはコマンドライン引き数で指定された名前空間を作成し、作成した名
       前空間内でコマンドを実行する。 コメントとプログラム内の usage() 関数に、プログラムの詳しい
       説明が書かれている。 以下のシェルセッションに実行例を示す。

       まず最初に、実行環境を確認しておく。

           $ uname -rs     # Linux 3.8 以降が必要
           Linux 3.8.0
           $ id -u         # 非特権ユーザーで実行する
           1000
           $ id -g
           1000

       新しいユーザー名前空間 (-U), マウント名前空間 (-m), PID 名前空間 (-p) で新しいシェルを開始
       する。ユーザー  ID (-M) 1000 とグループ ID (-G) 1000 をユーザー名前空間内で 0 にマッピング
       している。

           $ ./userns_child_exec -p -m -U -M '0 1000 1' -G '0 1000 1' bash

       シェルは PID 1 を持つ。このシェルは新しい PID 名前空間の最初のプロセスだからである。

           bash$ echo $$
           1

       ユーザー名前空間内では、シェルのユーザー ID とグループ ID ともに 0  で、すべての許可ケーパ
       ビリティと実効ケーパビリティが有効になっている。

           bash$ cat /proc/$$/status | egrep '^[UG]id'
           Uid: 0    0    0    0
           Gid: 0    0    0    0
           bash$ cat /proc/$$/status | egrep '^Cap(Prm|Inh|Eff)'
           CapInh:   0000000000000000
           CapPrm:   0000001fffffffff
           CapEff:   0000001fffffffff

       /proc  ファイルシステムをマウントし、新しい PID 名前空間で見えるプロセス一覧を表示すると、
       シェルからは PID 名前空間外のプロセスが見えないことが分かる。

           bash$ mount -t proc proc /proc
           bash$ ps ax
             PID TTY      STAT   TIME COMMAND
               1 pts/3    S      0:00 bash
              22 pts/3    R+     0:00 ps ax

   プログラムのソース

       /* userns_child_exec.c

          GNU General Public License v2 以降の元でライセンスされる

          新しい名前空間でシェルコマンドを実行する子プロセスを作成する。
          ユーザー名前空間を作成する際に UID と GID のマッピングを
          指定することができる。
       */
       #define _GNU_SOURCE
       #include <sched.h>
       #include <unistd.h>
       #include <stdlib.h>
       #include <sys/wait.h>
       #include <signal.h>
       #include <fcntl.h>
       #include <stdio.h>
       #include <string.h>
       #include <limits.h>
       #include <errno.h>

       /* 簡単なエラー処理関数: \(aqerrno\(aq の値に基づいて
          エラーメッセージを出力し、呼び出し元プロセスを終了する。 */

       #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                               } while (0)

       struct child_args {
           char **argv;        /* 子プロセスが実行するコマンドと引き数 */
           int    pipe_fd[2];  /* 親プロセスと子プロセスを同期するためのパイプ */
       };

       static int verbose;

       static void
       usage(char *pname)
       {
           fprintf(stderr, "Usage: %s [options] cmd [arg...]\n\n", pname);
           fprintf(stderr, "Create a child process that executes a shell "
                   "command in a new user namespace,\n"
                   "and possibly also other new namespace(s).\n\n");
           fprintf(stderr, "Options can be:\n\n");
       #define fpe(str) fprintf(stderr, "    %s", str);
           fpe("-i          New IPC namespace\n");
           fpe("-m          New mount namespace\n");
           fpe("-n          New network namespace\n");
           fpe("-p          New PID namespace\n");
           fpe("-u          New UTS namespace\n");
           fpe("-U          New user namespace\n");
           fpe("-M uid_map  Specify UID map for user namespace\n");
           fpe("-G gid_map  Specify GID map for user namespace\n");
           fpe("-z          Map user's UID and GID to 0 in user namespace\n");
           fpe("            (equivalent to: -M '0 <uid> 1' -G '0 <gid> 1')\n");
           fpe("-v          Display verbose messages\n");
           fpe("\n");
           fpe("If -z, -M, or -G is specified, -U is required.\n");
           fpe("It is not permitted to specify both -z and either -M or -G.\n");
           fpe("\n");
           fpe("Map strings for -M and -G consist of records of the form:\n");
           fpe("\n");
           fpe("    ID-inside-ns   ID-outside-ns   len\n");
           fpe("\n");
           fpe("A map string can contain multiple records, separated"
               " by commas;\n");
           fpe("the commas are replaced by newlines before writing"
               " to map files.\n");

           exit(EXIT_FAILURE);
       }

       /* マッピングファイル 'map_file' を 'mapping' で指定
          された値で更新する。 'mapping' は UID や GID マッピングを
          定義する文字列である。 UID や GID マッピングは以下の形式の改行
          で区切られた 1 つ以上のレコードである。

              NS 内 ID        NS 外 ID        長さ

          ユーザーに改行を含む文字列を指定するのを求めるのは、
          コマンドラインを使う場合にはもちろん不便なことである。
          そのため、 この文字列でレコードを区切るのにカンマを
          使えるようにして、ファイルにこの文字列を書き込む前に
          カンマを改行に置換する。 */

       static void
       update_map(char *mapping, char *map_file)
       {
           int fd, j;
           size_t map_len;     /* 'mapping' の長さ */

           /* マッピング文字列内のカンマを改行で置換する */

           map_len = strlen(mapping);
           for (j = 0; j < map_len; j++)
               if (mapping[j] == ',')
                   mapping[j] = '\n';

           fd = open(map_file, O_RDWR);
           if (fd == -1) {
               fprintf(stderr, "ERROR: open %s: %s\n", map_file,
                       strerror(errno));
               exit(EXIT_FAILURE);
           }

           if (write(fd, mapping, map_len) != map_len) {
               fprintf(stderr, "ERROR: write %s: %s\n", map_file,
                       strerror(errno));
               exit(EXIT_FAILURE);
           }

           close(fd);
       }

       static int              /* クローンされた子プロセスの開始関数 */
       childFunc(void *arg)
       {
           struct child_args *args = (struct child_args *) arg;
           char ch;

           /* 親プロセスが UID と GID マッピングを更新するまで待つ。
              main() のコメントを参照。 パイプの end of file を待つ。
              親プロセスが一旦マッピングを更新すると、
              パイプはクローズされる。 */

           close(args->pipe_fd[1]);    /* パイプのこちら側の書き込み端のディスク
                                              リプターをクローズする。これにより
                                              親プロセスがディスクリプターをクローズ
                                              すると EOF が見えるようになる。 */
           if (read(args->pipe_fd[0], &ch, 1) != 0) {
               fprintf(stderr,
                       "Failure in child: read from pipe returned != 0\n");
               exit(EXIT_FAILURE);
           }

           /* シェルコマンドを実行する */

           printf("About to exec %s\n", args->argv[0]);
           execvp(args->argv[0], args->argv);
           errExit("execvp");
       }

       #define STACK_SIZE (1024 * 1024)

       static char child_stack[STACK_SIZE];    /* 子プロセスのスタック空間 */

       int
       main(int argc, char *argv[])
       {
           int flags, opt, map_zero;
           pid_t child_pid;
           struct child_args args;
           char *uid_map, *gid_map;
           const int MAP_BUF_SIZE = 100;
           char map_buf[MAP_BUF_SIZE];
           char map_path[PATH_MAX];

           /* コマンドラインオプションを解析する。
              最後の getopt() 引き数の最初の '+' 文字は
              GNU 風のコマンドラインオプションの並び換えを防止する。
              このプログラム自身が実行する「コマンド」にコマンドライン
              オプションが含まれる場合があるからである。
              getopt() にこれらをこのプログラムのオプションとして
              扱ってほしくはないのだ。 */

           flags = 0;
           verbose = 0;
           gid_map = NULL;
           uid_map = NULL;
           map_zero = 0;
           while ((opt = getopt(argc, argv, "+imnpuUM:G:zv")) != -1) {
               switch (opt) {
               case 'i': flags |= CLONE_NEWIPC;        break;
               case 'm': flags |= CLONE_NEWNS;         break;
               case 'n': flags |= CLONE_NEWNET;        break;
               case 'p': flags |= CLONE_NEWPID;        break;
               case 'u': flags |= CLONE_NEWUTS;        break;
               case 'v': verbose = 1;                  break;
               case 'z': map_zero = 1;                 break;
               case 'M': uid_map = optarg;             break;
               case 'G': gid_map = optarg;             break;
               case 'U': flags |= CLONE_NEWUSER;       break;
               default:  usage(argv[0]);
               }
           }

           /* -U なしの -M や -G の指定は意味がない */

           if (((uid_map != NULL || gid_map != NULL || map_zero) &&
                       !(flags & CLONE_NEWUSER)) ||
                   (map_zero && (uid_map != NULL || gid_map != NULL)))
               usage(argv[0]);

           args.argv = &argv[optind];

           /* 親プログラムと子プロセスを同期するためにパイプを使っている。
              これは、子プロセスが execve() を呼び出す前に、親プロセスにより
              UID と GID マップが設定されることを保証するためである。
              これにより、新しいユーザー名前空間において子プロセスの実効
              ユーザー ID を 0 にマッピングしたいという通常の状況で、
              子プロセスが execve() 実行中にそのケーパビリティを維持する
              ことができる。 この同期を行わないと、 0 以外のユーザー ID で
              execve() を実行した際に、子プロセスがそのケーパビリティを失う
              ことになる (execve() 実行中のプロセスのケーパビリティの変化の
              詳細については capabilities(7) マニュアルページを参照)。 */

           if (pipe(args.pipe_fd) == -1)
               errExit("pipe");

           /* 新しい名前空間で子プロセスを作成する */

           child_pid = clone(childFunc, child_stack + STACK_SIZE,
                             flags | SIGCHLD, &args);
           if (child_pid == -1)
               errExit("clone");

           /* 親プロセスはここを実行する */

           if (verbose)
               printf("%s: PID of child created by clone() is %ld\n",
                       argv[0], (long) child_pid);

           /* 子プロセスの UID と GID のマッピングを更新する */

           if (uid_map != NULL || map_zero) {
               snprintf(map_path, PATH_MAX, "/proc/%ld/uid_map",
                       (long) child_pid);
               if (map_zero) {
                   snprintf(map_buf, MAP_BUF_SIZE, "0 %ld 1", (long) getuid());
                   uid_map = map_buf;
               }
               update_map(uid_map, map_path);
           }
           if (gid_map != NULL || map_zero) {
               snprintf(map_path, PATH_MAX, "/proc/%ld/gid_map",
                       (long) child_pid);
               if (map_zero) {
                   snprintf(map_buf, MAP_BUF_SIZE, "0 %ld 1", (long) getgid());
                   gid_map = map_buf;
               }
               update_map(gid_map, map_path);
           }

           /* パイプの書き込み端をクローズし、子プロセスに UID と GID の
              マッピングが更新されたことを知らせる */

           close(args.pipe_fd[1]);

           if (waitpid(child_pid, NULL, 0) == -1)      /* 子プロセスを待つ */
               errExit("waitpid");

           if (verbose)
               printf("%s: terminating\n", argv[0]);

           exit(EXIT_SUCCESS);
       }

関連項目

       newgidmap(1), newuidmap(1), clone(2), setns(2), unshare(2), proc(5), subgid(5), subuid(5),
       credentials(7), capabilities(7), namespaces(7), pid_namespaces(7)

       カーネルのソースファイル Documentation/namespaces/resource-control.txt

この文書について

       この  man ページは Linux man-pages プロジェクトのリリース 3.79 の一部 である。プロジェクト
       の説明とバグ報告に関する情報は http://www.kernel.org/doc/man-pages/ に書かれている。