Skip to content

Latest commit

 

History

History
185 lines (177 loc) · 8.13 KB

6.3.md

File metadata and controls

185 lines (177 loc) · 8.13 KB

Linuxシステムプログラミング入門 事前学習#6.3 User名前空間

概要

Linuxでは複数のユーザやグループを扱うことができます.このユーザやグループを分離するのがUser名前空間です.

マッピング

元のUser名前空間のユーザやグループと新しいUser名前空間のユーザやグループを対応づけることができます.これを利用すると,元のUser名前空間の一般ユーザが新しいUser名前空間ではrootになったりすることができます. 対応付けられていないユーザやグループはnobodyという名前のユーザやグループに見えます.nobodyユーザのユーザIDやnobodyグループのグループIDは65534です.

uid_map

ユーザIDのマッピングは,/procにあるファイルを利用して行います.プロセスIDが1234のとき,このファイルは/proc/1234/uid_mapです.このファイルはプロセスごとに存在し,元の名前空間にあるプロセスから,ただ1回だけ書き込むことができます.このファイルは改行で区切られた数行からなり,各行はスペース区切りの3つの整数からなります.これらの意味するところは,前から順に新しい名前空間でのユーザID,元の名前空間でのユーザID,マッピングするユーザIDの数となっています. 例として以下のようなマッピングを考えてみましょう.

0 1000 3

このマッピングは,以下のように元の名前空間でのユーザIDと新しい名前空間でのユーザIDを対応付けます.

元の名前空間 新しい名前空間
1000 0
1001 1
1002 2

パーミッション

ファイルのパーミッションもすべて上で述べたマッピングにより対応付けられます. 例として,次のような場合を考えてみます.元の名前空間でのminicampというユーザが新しい名前空間でrootユーザにマッピングされているとします.元の名前空間で所有者がminicampだったファイルは新しい名前空間ではrootとなります.逆に,新しい名前空間でrootを所有者として新しいファイルを作成すると,元の名前空間ではそのファイルの所有者はminicampに見えます.元の名前空間でのsyslogというユーザが新しい名前空間にマッピングされていないとすると,所有者がsyslogのファイルは,新しい名前空間では所有者はnobodyに見えます.

権限は不変

User名前空間を利用してユーザやグループが別のものになっても,ファイルなどの権限は変わりません.つまり,見かけ上は別のユーザですが,できることは全く同じです.元のUser名前空間での一般ユーザが新しいUser名前空間でrootになっても,もともと権限が無かった他のユーザのファイルにアクセスしたり,ネットワーク設定などを変更することはできません.nobodyユーザなどについてもこれは当てはまり,元の名前空間の複数のユーザが新しい名前空間から同じnobodyユーザとして見えていたとしても,実際にはそれらは別のユーザとして権限制御がなされます.

利用方法

cloneシステムコールにCLONE_NEWUSERフラグを設定すると,新しいUser名前空間でプロセスが開始されます.

clone(child_func,stack+1024*1024,SIGCHLD|CLONE_NEWUSER,arg);

演習

好きな場所にns_user_exampleというディレクトリを作成し,その中に以下のようなディレクトリ構造を作成しましょう.

- [dir] ns_user_example
    - [dir] src
        - [file] main.c
    - [dir] build
    - [file] CMakeLists.txt

以下のCコードはmain.cに記述します.

getpidシステムコール

getpidシステムコールは呼び出したプロセスを実行しているユーザのユーザIDを返します.

uid_t getuid(void);

ユーザIDを標準出力に出力するには,以下のように記述します.

printf("UID=%d\n",getuid());

PIDを出力する関数の記述

getuidを利用して以下のような関数を記述しましょう.

int print_uid(char* s){
	printf("[%s]UID=%d\n",s?s:"",getuid());
}

main関数の記述

以下のようなmain関数を記述します.

int main(){
	print_uid("parent");
}

ヘッダのインクルード

以下の必要なヘッダファイルをインクルードします.

#include <unistd.h>
#include <stdio.h>

ここまでのCコードのまとめ

ここまでを行うと以下のようになっていると思います.

#include <unistd.h>
#include <stdio.h>

int print_uid(char* s){
	printf("[%s]UID=%d\n",s?s:"",getuid());
}

int main(){
	print_uid("parent");
}

CMakeLists.txtの記述

以下のようにCMakeLists.txtに記述します.

cmake_minimum_required(VERSION 3.16)
project(ns_user_example)
set(CMAKE_C_STANDARD 11)

add_executable(example src/main.c)

ビルドと実行

buildディレクトリに移動します.

$ cd ./build

cmakeを実行します.CMakeLists.txtを含むディレクトリ,今回はns_user_exampleディレクトリを..で指定しています.

$ cmake ..

makeを実行します.

$ make

exampleというバイナリが生成されるのでこれを実行します.以前PID名前空間のときはroot権限が必要でしたが,User名前空間はroot権限が不要であることは大きな差異です.

$ ./example

ユーザIDが出力されます.

[parent]UID=1011

子プロセスの実行

子プロセスを新しいUser名前空間で実行するには,cloneシステムコールにCLONE_NEWPIDフラグを指定します.cloneの詳細については別ページで述べました.

pid_t c = clone((int(*)(void*))print_uid,stack+1024*1024,CLONE_NEWPID|SIGCHLD,"child");

(int(*)(void*))print_uidという部分は,prind_uid関数へのポインタをint func(char*)の関数のポインタの型にキャストしています. スタックの準備などを付け加え,以下のようにmain関数を変更します.

int main(){
	print_pid("parent");
	void* stack = mmap(NULL,1024*1024,PROT_READ|PROT_WRITE,
            MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_STACK,-1,0);
	pid_t c = clone((int(*)(void*))print_uid,stack+1024*1024,CLONE_NEWUSER|SIGCHLD,"child");
	if(c == -1){
		puts("clone failed!\n");
		return -1;
	}
	waitpid(c,NULL,0);
}

追加のヘッダのインクルード

以下のヘッダファイルを追加でインクルードします.

#define _GNU_SOURCE
#include <sched.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>

Cコードのまとめ

ここまでをすべて行うと次のようになります.

#define _GNU_SOURCE
#include <sched.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <stdio.h>

int print_uid(char* s){
	printf("[%s]UID=%d\n",s?s:"",getuid());
}

int main(){
	print_uid("parent");
	void* stack = mmap(NULL,1024*1024,PROT_READ|PROT_WRITE,
            MAP_PRIVATE|MAP_ANONYMOUS|MAP_GROWSDOWN|MAP_STACK,-1,0);
	pid_t c = clone((int(*)(void*))print_uid,stack+1024*1024,CLONE_NEWUSER|SIGCHLD,"child");
	if(c == -1){
		puts("clone failed!\n");
		return -1;
	}
	waitpid(c,NULL,0);
}

ビルドと実行

前と同様に,ビルドして実行します.

$ cd ./build
$ make
$ ./example

以下のように結果が出力されると思います.ここでもroot権限が不要であることに注目してください.

[parent]UID=1011
[child]UID=65534

新しく作成されたプロセスを実行しているユーザのユーザIDは65534であることがわかります.今回はまったくユーザIDのマッピングを行わなかったので,新しい名前空間ではnobodyユーザになっているということを示します.