TenForward

技術ブログ。はてなダイアリーから移転しました

Linux 3.8 の User Namespace 機能 (4)

前回で 3.9 kernel での準備が出来たので,今回は少しだけユーザ名前空間を体験してみました.

なお,今回の評価はもうすぐ新しいバージョン 5.1 がリリース予定の Plamo Linux 5.1beta1 相当の環境上で行っています(宣伝).ここに自前でビルドした 3.9 カーネルと,util-linux の現時点の最新版である 2.23 をインストールしています.

ユーザ名前空間の体験は

が参考になります.というか今回のエントリはここに書いてある事を実行しているだけです.(^_^;) 最初の方に書いてある Ubuntu を使っての ppa からの色々なインストールはうまく行かないものがありました

まずはここにある nsexec というツール群を取得します.これは make を実行すればコンパイルは完了です.

usernsselfmap コマンドの処理

コンパイルが完了したところでまずは上記ページにあるように "usernsselfmap" コマンドを実行します.

このプログラムは非常に単純なものです.

int main(int argc, char *argv[])
{
  char *args[] = { "/bin/bash", NULL };
  int ret;
  int origuid = getuid();
  int origgid = getgid();

  ret = unshare(CLONE_NEWUSER);
  ret = writemaps(getpid(), origuid, origgid);
  if (ret < 0) {
    printf("Error writing maps\n");
    exit(1);
  }
  if (ret < 0) {
    perror("unshare");
    exit(1);
  }
  ret = setgid(0);
  if (ret < 0)
    perror("setgid");
  ret = setuid(0);
  if (ret < 0)
    perror("setuid");
  ret = setgroups(0, NULL);
  if (ret < 0)
    perror("setgroups");
  printf("execing bash (I am  now %d %d)\n", getuid(), getgid());
  execv(args[0], args);
  exit(1);
}

この手のプログラミング知識のない私でも一瞬で分かる程度に簡単なコードです.(ソースのインデントがぐちゃぐちゃで見にくい...)

  1. unshare で CLONE_NEWUSER を指定して,新しいユーザ名前空間を作成してプロセスをそちらに移行します.(man 2 unshare)
  2. writemaps は uid_map と gid_map を作成し,新しい名前空間の uid/gid と元の実行コンテキストの uid/gidマッピングを行います (詳細はソースを参照)
  3. 名前空間内での uid/gid を 0/0 に設定します
  4. execv で名前空間内で /bin/bash を実行します

ユーザ名前空間の作成

さて,何をやるコマンドか把握できたところで実行してみます.

  1. まずは現在の id を確認します.uid/gid = 1000/100 のユーザです.
    karma@plamo50:~/src/nsexec/nsexec$ id
    uid=1000(karma) gid=100(users) groups=100(users),26(audio),28(dialout),29(video),32(cdrom),38(pulse),39(pulse-access),44(mlocate),1000(sudo)
  2. usernsselfmap 実行
    karma@plamo50:~/src/nsexec/nsexec$ ./usernsselfmap 
    starting from uid 1000 gid 100
    setuid: Success
    execing bash (I am now 0 0)
  3. 新しいユーザ名前空間bash が実行されていますので,現在の uid/gid を確認しておきます.root ユーザになっています.
    root@plamo50:~/src/nsexec/nsexec# id
    uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
  4. uid_map, gid_map を確認してみます.uid/gid=1000/100 を 0/0 へマッピングしています
    root@plamo50:~/src/nsexec/nsexec# echo $$
    7771
    root@plamo50:~/src/nsexec/nsexec# cat /proc/7771/uid_map
    0 1000 1
    root@plamo50:~/src/nsexec/nsexec# cat /proc/7771/gid_map
    0 100 1
  5. ファイルを作成してみます.当たり前ですが,所有者が root のファイルが出来上がります
    root@plamo50:~/src/nsexec/nsexec# touch test
    root@plamo50:~/src/nsexec/nsexec# ls -l test
    -rw-r--r-- 1 root root 0 5月 8日 19:10 test

さて,上記で作ったファイルを元の名前空間で見てみるとどうなっているでしょうか.皆さんの予想通り

karma@plamo50:~/src/nsexec/nsexec$ ls -l test
-rw-r--r-- 1 karma users 0  5月  8日  19:10 test

元の uid/gid の所有になっています.

ユーザ名前空間の確認

上記でファイルの作成を試しましたが,ここではユーザ名前空間の効果がはっきり分かる実験をしてみます.これも先に紹介したブログにありますけど...

  1. 作成したユーザ名前空間内でネットワークインターフェースを確認します.ネットワーク名前空間の作成は行っていないので,ここでは元のコンテキストと同じなので,usernsselfmap 実行前と同じインターフェースが見えます.
    root@plamo50:~/src/nsexec/nsexec# /sbin/ifconfig
    eth0 Link encap:Ethernet HWaddr 52:54:00:59:7f:7e
    inet addr:192.168.122.149 Bcast:192.168.122.255 Mask:255.255.255.0
    inet6 addr: fe80::5054:ff:fe59:7f7e/64 Scope:Link
    UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
    RX packets:7631 errors:0 dropped:19 overruns:0 frame:0
    TX packets:2947 errors:0 dropped:0 overruns:0 carrier:0
    collisions:0 txqueuelen:1000
    RX bytes:3682267 (3.5 MiB) TX bytes:367002 (358.4 KiB)
    : (snip)
  2. root であることを確認した後に ifconfig down を実行してみます.
    root@plamo50:~/src/nsexec/nsexec# id
    uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
    root@plamo50:~/src/nsexec/nsexec# /sbin/ifconfig eth0 down
    SIOCSIFFLAGS: 許可されていない操作です
    root なのに許可されていない操作でエラーになっています :-)

以上でユーザ名前空間の実験は終わりですが,先のブログエントリの実行例にあったように作成したユーザ名前空間で更にネットワーク名前空間とマウント名前空間を作成してみました.

ネットワーク名前空間の作成

この作成には util-linux に含まれている unshare コマンドを使ってみました (使い方は man 1 unshare 参照).元のブログにあるように実験用に取得した nsexec でも同じような事ができるので OK です (nsexec -h で使い方でます).

作成したユーザ名前空間内で新しくネットワーク名前空間を作成して bash を実行しています.

karma@plamo50:~/src/nsexec/nsexec$ ./usernsselfmap 
starting from uid 1000 gid 100
setuid: Success
execing bash (I am  now 0 0)
root@plamo50:~/src/nsexec/nsexec# echo $$
7803
root@plamo50:~/src/nsexec/nsexec# unshare -n /bin/bash
root@plamo50:~/src/nsexec/nsexec# echo $$
7907
root@plamo50:~/src/nsexec/nsexec# ls -l /proc/7803/ns
合計 0
lrwxrwxrwx 1 root root 0  5月  8日  19:27 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0  5月  8日  19:27 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0  5月  8日  19:27 net -> net:[4026531955]
lrwxrwxrwx 1 root root 0  5月  8日  19:27 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0  5月  8日  19:27 user -> user:[4026532162]
lrwxrwxrwx 1 root root 0  5月  8日  19:27 uts -> uts:[4026531838]
root@plamo50:~/src/nsexec/nsexec# ls -l /proc/7907/ns
合計 0
lrwxrwxrwx 1 root root 0  5月  8日  19:32 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 root root 0  5月  8日  19:32 mnt -> mnt:[4026531840]
lrwxrwxrwx 1 root root 0  5月  8日  19:32 net -> net:[4026532164]
lrwxrwxrwx 1 root root 0  5月  8日  19:32 pid -> pid:[4026531836]
lrwxrwxrwx 1 root root 0  5月  8日  19:32 user -> user:[4026532162]
lrwxrwxrwx 1 root root 0  5月  8日  19:32 uts -> uts:[4026531838]

名前空間ファイルを見てみると net だけ別の名前空間にいることがわかります.ここで ifconfig を実行するとインターフェースは存在しません.新しいネットワーク名前空間なので当然です.

root@plamo50:~/src/nsexec/nsexec# /sbin/ifconfig

このユーザ名前空間内では root 権限ですから,新しく作ったネットワーク名前空間でインターフェースの操作は可能なはずです.ここで veth を作ってアドレスを割り当ててみます.

root@plamo50:~/src/nsexec/nsexec# /sbin/ip link add type veth
root@plamo50:~/src/nsexec/nsexec# /sbin/ip link show
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 2e:af:82:91:91:e6 brd ff:ff:ff:ff:ff:ff
3: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether be:a9:a9:2d:1e:19 brd ff:ff:ff:ff:ff:ff
root@plamo50:~/src/nsexec/nsexec# /sbin/ifconfig veth0 10.10.10.10 up
root@plamo50:~/src/nsexec/nsexec# /sbin/ifconfig
veth0     Link encap:Ethernet  HWaddr 2e:af:82:91:91:e6  
          inet addr:10.10.10.10  Bcast:10.255.255.255  Mask:255.0.0.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

この時,元の名前空間上ではインターフェースを見ると

root@plamo50:/home/karma/src/nsexec/nsexec# ifconfig | grep "Link encap"
eth0      Link encap:Ethernet  HWaddr 52:54:00:59:7f:7e  
lo        Link encap:Local Loopback  
lxcbr0    Link encap:Ethernet  HWaddr 36:da:e3:2f:6f:c8  

となっており,当然ですが新しく作ったネットワーク名前空間のインターフェースは見えません.

マウント名前空間

同じようなことをマウント名前空間で試してみます.新しく作成したユーザ名前空間は,元の名前空間上では uid/gid = 1000/100 ですから,名前空間では uid/gid=0/0 の root ユーザでも /root ディレクトリ内を見ることはできません.

root@plamo50:~/src/nsexec/nsexec# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
root@plamo50:~/src/nsexec/nsexec# ls /root
ls: ディレクトリ /root を開くことが出来ません: 許可がありません

ここで新たにマウント名前空間を作成して,以下のようなことをしてみると,元のユーザ (uid/gid = 1000/100) のホームディレクトリの内容が /root として見えます.

root@plamo50:~/src/nsexec/nsexec# unshare -m /bin/bash
root@plamo50:~/src/nsexec/nsexec# mount --bind $HOME /root
root@plamo50:~/src/nsexec/nsexec# ls /root
  (元のユーザの $HOME の内容が表示される)

(おまけ) util-linux の unshare コマンド

util-linux の unshare コマンドもコードは非常に単純ですから確認してみてください.sys-utils/unshare.c

ロケールの処理をやって,オプションでどの名前空間を作成するかを受けとって unshare(2) を呼んでいるだけです.^^;