前回の日記でふと思ったuser_namespace構造体とは何?というのを調べているんだけどその過程で出てきたkuid_tとkgid_tのメモ。
定義自体はinclude/linux/uidgid.hにあって、このような形でとくに面白いものでもない。
20 typedef struct { 21 uid_t val; 22 } kuid_t; 23 24 25 typedef struct { 26 gid_t val; 27 } kgid_t;
これらがどのような目的で使われているのかというのが知りたいところですよね。もしかしたらLWNに解説記事があるんじゃないかな〜と思って見てみたらありました! このA new approach to user namespacesがビンゴですね。 記事によるとこれらがカーネルに入ったのは2012年の秋頃なので割と最近のことみたいです。目的はコンテナ型仮想化の効率的なサポートというところみたいです。
kernel UIDはプロセスを識別するのだけど、これはホスト側のため(原文だと the base systemだからホスト側かな)に使うのでコンテナ内で使われるUIDとは別物。これは主に権限チェックのために使用する。プロセスからはこの値を知ることはできない。ホントにカーネルが使うためのid。
kuid_t、kgid_tを操作する関数としてはこれらがあるけど(比較系の関数もある)、概要レベルではやりたいことが想像つくからとりあえず読まなくて良いか。
110 extern kuid_t make_kuid(struct user_namespace *from, uid_t uid); 111 extern kgid_t make_kgid(struct user_namespace *from, gid_t gid); 112 113 extern uid_t from_kuid(struct user_namespace *to, kuid_t uid); 114 extern gid_t from_kgid(struct user_namespace *to, kgid_t gid); 115 extern uid_t from_kuid_munged(struct user_namespace *to, kuid_t uid); 116 extern gid_t from_kgid_munged(struct user_namespace *to, kgid_t gid); 117
で、このIDがどのように使われているのかを見てみる。lxrで調べるとfs/open.cにあるchown_common()で使っているのでこの辺を。
関数の引数にあるuserとgroupはchmod時に使うuidとgidです。
549 static int chown_common(struct path *path, uid_t user, gid_t group) 550 { 551 struct inode *inode = path->dentry->d_inode; 552 struct inode *delegated_inode = NULL; 553 int error; 554 struct iattr newattrs; 555 kuid_t uid; 556 kgid_t gid; 557 558 uid = make_kuid(current_user_ns(), user); 559 gid = make_kgid(current_user_ns(), group); 560 561 newattrs.ia_valid = ATTR_CTIME; 562 if (user != (uid_t) -1) { 563 if (!uid_valid(uid)) 564 return -EINVAL; 565 newattrs.ia_valid |= ATTR_UID; 566 newattrs.ia_uid = uid; 567 }
chown系の場合はカレントタスクのuser_nsと引数で渡ってきた設定したいuser idのからkuidのマッピングを調べて、この値が-1じゃない場合は562行目からのifブロック内の処理に入る。
この場合、uidがinvalidな可能性もあるのでチェックが入っている。これを見ると-1が返るのはどんな場合なのか気になるのでやっぱりmake_kuid()を見てみよう。
make_kuid()はこれ。KUIDT_INITはkuid_tの変数を作ってメンバ変数のvalに値を設定するマクロなので本体はmap_id_down()。
218 /** 219 * make_kuid - Map a user-namespace uid pair into a kuid. 220 * @ns: User namespace that the uid is in 221 * @uid: User identifier 222 * 223 * Maps a user-namespace uid pair into a kernel internal kuid, 224 * and returns that kuid. 225 * 226 * When there is no mapping defined for the user-namespace uid 227 * pair INVALID_UID is returned. Callers are expected to test 228 * for and handle INVALID_UID being returned. INVALID_UID 229 * may be tested for using uid_valid(). 230 */ 231 kuid_t make_kuid(struct user_namespace *ns, uid_t uid) 232 { 233 /* Map the uid to a global kernel uid */ 234 return KUIDT_INIT(map_id_down(&ns->uid_map, uid)); 235 }
map_id_down()はこれ。
172 static u32 map_id_down(struct uid_gid_map *map, u32 id) 173 { 174 unsigned idx, extents; 175 u32 first, last; 176 177 /* Find the matching extent */ 178 extents = map->nr_extents; 179 smp_rmb(); 180 for (idx = 0; idx < extents; idx++) { 181 first = map->extent[idx].first; 182 last = first + map->extent[idx].count - 1; 183 if (id >= first && id <= last) 184 break; 185 } 186 /* Map the id or note failure */ 187 if (idx < extents) 188 id = (id - first) + map->extent[idx].lower_first; 189 else 190 id = (u32) -1; 191 192 return id; 193 }
やっていることは見ての通りという気も。使っているデータはuid_gid_map構造体でこれはinclude/linux/user_namespace.hでこのように定義されている。
9 #define UID_GID_MAP_MAX_EXTENTS 5 10 11 struct uid_gid_map { /* 64 bytes -- 1 cache line */ 12 u32 nr_extents; 13 struct uid_gid_extent { 14 u32 first; 15 u32 lower_first; 16 u32 count; 17 } extent[UID_GID_MAP_MAX_EXTENTS]; 18 };
このmapはどこかで初期化されているんだろけどというのを調べるとkernel/user.cにinit_user_nsの定義が。これが基本的に使われるやつかな。kernel/cred.cにあるinit_credの定義でも設定されているし。
21 /* 22 * userns count is 1 for root user, 1 for init_uts_ns, 23 * and 1 for... ? 24 */ 25 struct user_namespace init_user_ns = { 26 .uid_map = { 27 .nr_extents = 1, 28 .extent[0] = { 29 .first = 0, 30 .lower_first = 0, 31 .count = 4294967295U, 32 }, 33 }, 34 .gid_map = { 35 .nr_extents = 1, 36 .extent[0] = { 37 .first = 0, 38 .lower_first = 0, 39 .count = 4294967295U, 40 }, 41 }, 42 .projid_map = { 43 .nr_extents = 1, 44 .extent[0] = { 45 .first = 0, 46 .lower_first = 0, 47 .count = 4294967295U, 48 }, 49 }, 50 .count = ATOMIC_INIT(3), 51 .owner = GLOBAL_ROOT_UID, 52 .group = GLOBAL_ROOT_GID, 53 .proc_inum = PROC_USER_INIT_INO, 54 #ifdef CONFIG_PERSISTENT_KEYRINGS 55 .persistent_keyring_register_sem = 56 __RWSEM_INITIALIZER(init_user_ns.persistent_keyring_register_sem), 57 #endif 58 }; 59 EXPORT_SYMBOL_GPL(init_user_ns);
これでmap_id_down()に戻ってループの箇所を見てみると、extentsは1で調べたいIDがmap->extent[0]のfirst~countの間にあった場合はbreakする。
180 for (idx = 0; idx < extents; idx++) { 181 first = map->extent[idx].first; 182 last = first + map->extent[idx].count - 1; 183 if (id >= first && id <= last) 184 break; 185 }
そして、ループを抜けたところで187行目のif分が真になるのはループをbreakで抜けた場合で-1が返るのはidがcountが0以下もしくはcount以上だった場合ということがわかる。
186 /* Map the id or note failure */ 187 if (idx < extents) 188 id = (id - first) + map->extent[idx].lower_first; 189 else 190 id = (u32) -1;
ということで、make_kuid(KUIDT_INIT&map_id_down()の組み合わせ)は割とシンプルな仕組みですね。これでユーザープロセスから見えるuidとカーネル用のkuidがマップされるわけですね。
ちなみにこの機能はCONFIG_USER_NS=yの時の話なのですが、うちの.configの元ネタの現時点でのarchの.configではこの機能は使ってませんでした。Fedora 21の.configでは有効になってます。
ここでまたLWNの記事戻る。このマッピングはシステム管理者が/proc/pid/uid_map(2014/08/07時点のtip treeだと/proc/pid/uid_mapでした)に値を書き込むことで行える。これで特定のidの範囲をコンテナ用に取っておくことができたりする。
/proc/pid/uid_mapは以下の3個の数値を設定できる。first-ns-idはuidの中でのvalidな最小値で、0が使用可能。uid0なのでrootということ。マッピングはfirst-target-idから開始される。
- first-ns-id
- first-target-id
- count
独自の名前空間でrootのプロセスとしてうごいているプロセスはその名前空間では大方の期待通りroot権限でうろくけど、もちろんその名前空間の外にはアクセスできない。user namespaceの下にさらに名前空間を作ることもできる。けど、親プロセスの名前空間にあるuid・gidのしか使うことはできない。また、一度設定したら更新はできない。
ざっとだけどこんなものかな。まだuser_namespaceは全然手付かずだけど。。
【改訂新版】Linuxエンジニア養成読本 [クラウド時代も、システムの基礎と基盤はLinux! ] (Software Design plus)
- 作者: 養成読本編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2014/03/18
- メディア: 大型本
- この商品を含むブログ (1件) を見る