カーネル4.3でのsetnsの実装を読みます。
setns()はkernel/nsproxy.cにあり、30行程度の短い関数です。 流れとしてはこのようになります。
- 移動先の名前空間のfdからns_common構造体を取得
- nsproxyの作成
- 移動先名前空間のinstall()を呼んで名前空間のほうに移動の処理をしてもらう
- 既存のnsproxyと新しく作ったnsproxyを切り替え
setns(2)のmanには名前空間ごとに制限がありますが、それらの制限はinstall()のほうで行っています。
setnsの実装はこのようになってます。
221 SYSCALL_DEFINE2(setns, int, fd, int, nstype) 222 { 223 struct task_struct *tsk = current; 224 struct nsproxy *new_nsproxy; 225 struct file *file; 226 struct ns_common *ns; 227 int err; 228 229 file = proc_ns_fget(fd); 230 if (IS_ERR(file)) 231 return PTR_ERR(file); 232 233 err = -EINVAL; 234 ns = get_proc_ns(file_inode(file)); 235 if (nstype && (ns->ops->type != nstype)) 236 goto out; 237 238 new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs); 239 if (IS_ERR(new_nsproxy)) { 240 err = PTR_ERR(new_nsproxy); 241 goto out; 242 } 243 244 err = ns->ops->install(new_nsproxy, ns); 245 if (err) { 246 free_nsproxy(new_nsproxy); 247 goto out; 248 } 249 switch_task_namespaces(tsk, new_nsproxy); 250 out: 251 fput(file); 252 return err; 253 }
ここはファイルディスクリプタからfile構造体を取得しています。
229 file = proc_ns_fget(fd);
ここで、file構造体からinode構造体にアクセスし、inode構造体のi_private変数に設定されているns_common構造体を取得します。
234 ns = get_proc_ns(file_inode(file));
get_proc_nsはこのようなマクロです。
69 #define get_proc_ns(inode) ((struct ns_common *)(inode)->i_private)
つぎにcreate_new_namespaces()でtask_structに設定するNSProxy構造体を作成します。1番目の引数に0を渡しているので名前空間の分離はなく、各名前空間の参照数が1増えます。
238 new_nsproxy = create_new_namespaces(0, tsk, current_user_ns(), tsk->fs);
名前空間の移動に関する処理はproc_ns_operations構造体に設定されているinstall()で行います。
244 err = ns->ops->install(new_nsproxy, ns);
最後にcurrent->nsproxyを先ほど作ったnew_nsproxyに置き換えて完了です。
249 switch_task_namespaces(tsk, new_nsproxy);
では、install()の処理を簡単に見てみます。まずは簡単なところでuts名前空間を。実装はkernel/utsname.cにあります。
実装はこうなっています。わかりやすいですね。
119 static int utsns_install(struct nsproxy *nsproxy, struct ns_common *new) 120 { 121 struct uts_namespace *ns = to_uts_ns(new); 122 123 if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN) || 124 !ns_capable(current_user_ns(), CAP_SYS_ADMIN)) 125 return -EPERM; 126 127 get_uts_ns(ns); 128 put_uts_ns(nsproxy->uts_ns); 129 nsproxy->uts_ns = ns; 130 return 0; 131 } 132
utsns_install()は最初に現在の名前空間と移動先の名前空間でケーパビリティのCAP_SYS_ADMINがあるかチェックして、無ければエラーにします。
次は移動先の名前空間の参照数を1つ増やします。
127 get_uts_ns(ns);
その次は、このプロセスはに現在の名前空間から離れるので参照数を1つ減らします。
128 put_uts_ns(nsproxy->uts_ns);
最後にsetnsで作成したnsproxyのuts名前空間を移動先の名前空間に設定します。
129 nsproxy->uts_ns = ns;
以上がuts名前空間のinstall()処理です。NSProxyが管理している名前空間(net, mount, ipc, uts, pid)は基本的にこのような感じで名前空間の移動処理をします。 User名前空間はNSProxyが管理していないので多少処理が違います。User名前空間のinstall()はkernel/user_namespace.cにあります。
実装はこうなっていて、ほとんどが移動可能かのチェックになります。setnsでUser名前空間への参加ができない場合のルールはmanに書かれていて、このチェックをしています。
969 static int userns_install(struct nsproxy *nsproxy, struct ns_common *ns) 970 { 971 struct user_namespace *user_ns = to_user_ns(ns); 972 struct cred *cred; 973 974 /* Don't allow gaining capabilities by reentering 975 * the same user namespace. 976 */ 977 if (user_ns == current_user_ns()) 978 return -EINVAL; 979 980 /* Tasks that share a thread group must share a user namespace */ 981 if (!thread_group_empty(current)) 982 return -EINVAL; 983 984 if (current->fs->users != 1) 985 return -EINVAL; 986 987 if (!ns_capable(user_ns, CAP_SYS_ADMIN)) 988 return -EPERM; 989 990 cred = prepare_creds(); 991 if (!cred) 992 return -ENOMEM; 993 994 put_user_ns(cred->user_ns); 995 set_cred_user_ns(cred, get_user_ns(user_ns)); 996 997 return commit_creds(cred); 998 }
最初のto_user_ns()はns_common構造体からuser_namespace構造体へアクセスします。処理はこうなっています。container_ofマクロはLinuxカーネルではよく使うマクロですね。このマクロについては過去に書いたLinux: inodeからtask_struct構造体を取得 - φ(・・*)ゞ ウーン カーネルとか弄ったりのメモという記事で説明しました。
948 static inline struct user_namespace *to_user_ns(struct ns_common *ns) 949 { 950 return container_of(ns, struct user_namespace, ns); 951 }
User名前空間はcred構造体の管理下にあり、最初にこのcred構造体を作成します。
990 cred = prepare_creds();
ここは今のUser名前空間の参照数を1つ減らします。
994 put_user_ns(cred->user_ns);
次はget_user_ns()で移動先の名前空間の参照数を増やして、set_cred_user_ns()でcred構造体のuser名前空間の変数を移動先のものに設定します。
995 set_cred_user_ns(cred, get_user_ns(user_ns));
名前空間を設定しているのはset_cred_user_ns()の最後です。
33 static void set_cred_user_ns(struct cred *cred, struct user_namespace *user_ns) 34 { ~~~ 48 /* tgcred will be cleared in our caller bc CLONE_THREAD won't be set */ 49 cred->user_ns = user_ns; 50 }
最後にcommit_creds()でcurrentのcred構造体を差し替えます。この処理は名前空間の機能ではないのでこれくらいにしておきます。
ということで、setnsの処理とuts・user名前空間のinstall()を読んでみました。
Webサービスのつくり方 ――「新しい」を生み出すための33のエッセイ Software Design plus
- 作者: 和田裕介
- 出版社/メーカー: 技術評論社
- 発売日: 2013/07/10
- メディア: Kindle版
- この商品を含むブログ (4件) を見る