今年の目標は「はじめから無理と思わない」「基礎を学ぶ」なので、C言語からも逃げずに立ち向かう1年にしたい。 本日は、Linuxのスレッド内でchrootした場合の影響範囲を調べたのでまとめる。
はじめに
本エントリではLinuxでスレッドを作成して、システムコールの “chroot()” を実行した場合にスレッド内のみchrootされるのか?それとも、プロセス全体に影響するのかを調査していく。
実際に動かして確認する
スレッドを作成して、chrootを実行するプログラムをC言語で作成する。
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
void* thread_1(void* param);
void* thread_2(void* param);
void* thread_3(void* param);
void* set_chroot(char* path);
void* show_files(char* path);
int main(int argc, char *argv[]){
pthread_t thread_id_1, thread_id_2, thread_id_3;
show_files("/");
# スレッドを作って順番に実行していく
pthread_create(&thread_id_1, NULL, thread_1, NULL);
pthread_join(thread_id_1,NULL);
pthread_create(&thread_id_2, NULL, thread_2, NULL);
pthread_join(thread_id_2,NULL);
pthread_create(&thread_id_3, NULL, thread_3, NULL);
pthread_join(thread_id_3,NULL);
show_files("/");
return 0;
}
void* thread_1(void* param){
show_files("/");
}
void* thread_2(void* param){
set_chroot("/tmp");
show_files("/");
}
void* thread_3(void* param){
show_files("/");
}
void* set_chroot(char* path){
printf("=== chroot ===\n");
if(chroot(path) < 0){
printf("chroot(%d) %s\n", errno, strerror(errno));
}
}
void* show_files(char* path){
DIR *dir;
struct dirent *dp;
dir=opendir(path);
for(dp=readdir(dir); dp!=NULL; dp=readdir(dir)){
printf("%s\n", dp->d_name);
}
closedir(dir);
}
上記の様にスレッドを作って、順番に処理させる。 “show_files()” は指定したディレクトリの中身を表示するメソッドで、“set_chroot()” は指定したディレクトリをchrootに設定するメソッドです。 以下のタイミングで “show_files( / )” でディレクトリ一覧を取得し、“set_chroot( /tmp )” を実行したタイミングでディレクトリ一覧が “/tmp” になるパターンを調べる。
- main関数実行直後
- スレッド1実行直後
- スレッド2実行直後
- スレッド3実行直後
- main関数終了直前
“touch /tmp/tmp_here” を実行し、tmpディレクトリと分かるようにファイルを配置しておく。
結果
[root@pmilter thread]# gcc -lpthread kume.c && ./a.out
## main関数実行直後
## => `/` が参照されている。
.
..
boot
dev
proc
run
:
## スレッド1実行直後
## => `/` が参照されている。
.
..
boot
dev
proc
run
:
## スレッド2実行直後
# chroot("/tmp")を実行
=== chroot ===
# => ここで、`/tmp` が参照されている。
.
..
tmp_here
## スレッド3実行直後
## => 以降も `/tmp` が参照されている。
.
..
tmp_here
## main関数実行直後
## => ここでも `/tmp` が参照されている。
.
..
tmp_here
結果としては、スレッド内で “chroot()” が実行されて以降は親プロセスを含めて全てにchrootが適応されていることが分かった。 もちろん、main関数内でchrootした場合も同様だった。
なぜ、スレッド内でchrootした場合に、そのプロセス全体がchroot状態になるのか?
- chroot()のソースを見てみる。
SYSCALL_DEFINE1(chroot, const char __user *, filename)
{
struct path path;
int error;
unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
retry:
error = user_path_at(AT_FDCWD, filename, lookup_flags, &path);
if (error)
goto out;
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;
error = -EPERM;
if (!ns_capable(current_user_ns(), CAP_SYS_CHROOT))
goto dput_and_out;
error = security_path_chroot(&path);
if (error)
goto dput_and_out;
set_fs_root(current->fs, &path);
error = 0;
dput_and_out:
path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
}
out:
return error;
}
“set_fs_root(current->fs, &path)” このようにchrootを設定していることが分かる。
- set_fs_root()のソースを見てみる。
void set_fs_root(struct fs_struct *fs, const struct path *path)
{
struct path old_root;
path_get(path);
spin_lock(&fs->lock);
write_seqcount_begin(&fs->seq);
old_root = fs->root;
fs->root = *path;
write_seqcount_end(&fs->seq);
spin_unlock(&fs->lock);
if (old_root.dentry)
path_put(&old_root);
}
“fs->root = *path;” このようにchrootされたディレクトリを格納していることが分かる。
上記にあった “fs_struct” 構造体は “task_struct” 構造体の中にリンクされている。
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
: (snip)
/* filesystem information */
struct fs_struct *fs;
: (snip)
}
“task_struct” 構造体の中にはスレッドの情報が格納される、 “thread_info” が存在する。 “thread_info” 構造体の中には、chrootされているpathを記録する “fs_struct” 構造体などは存在しない。
結論
以下の事実を元にすると
- “chroot()” 実行後は親プロセスを含む全てのスレッドはchroot状態となる。
- “chroot()” 実行時は、 “fs_stract” 構造体の “root” にchrootのpathが記録される。
- “fs_stract” 構造体はプロセスの情報が格納される、“task_struct” 構造体にリンクされている。
- “thread_info” 構造体の中には、chrootされているpathを記録する “fs_struct” 構造体は存在しない。
threadで"chroot()“を実行した場合もプロセスとして持っている “fs_stract” 構造体にchrootの情報を格納するため、 “chroot()” 実行後は親プロセスを含む全てのスレッドはchroot状態となると思われる。