chroot牢獄から脱獄
仙石浩明さんの日記で面白い記事を見つけました。chroot されたディレクトリから脱出してみるというものです。例えば/aaa/bbb/ccc/dddでchroot cccとすると、aaa,bbbにはアクセスできなくなります。chroot牢獄というらしいのですが、なんとその牢獄から脱獄してaaa,bbbに行けちゃうっていうことです。実際さってみると、いとも簡単に脱獄できました。
これを実装からみてみようと思います。外れているかもしれません。タスク構造他にはルートディレクトリ情報のrootとカレントディレクトリ情報のpwdのメンバーを有しています。例えば/aaa/bbb/ccc/dddでchroot cccとしたといたします。fs->rootはccc、fs->pwdはcccとなっています。(fs->pwdはchrootコマンドが設定していると思いますが・・・)
通常はfs->root配下にfs->pwdは存在することになりますが、chrootでfs->root配下以外にfs->pwdを設定することで、chroot牢獄をエスケープしようというわけです。
カーネルのchrootシステムコールはfs->rootを書き換えるだけです。従ってCでchrootシステムコールをコールしてcd..とする一連のプログラムを作成すればいいわけです。そのようなプロセスをchrootされているルート(ccc)から起動すると、子プロセスはfs->pwdを引き継ぎます。そうすると、fs->rootがddd、fs->pwdがcccとなるプロセスを作成することが可能となります。
p/s
fs->pwdがコンポーネントした保持していないのに、なぜフルパスで表示されるのだろうか?と思って調べると、どうやら__d_pathをコールすることでfs->pwdから親へと遡って表示させているようでした。またそれゆえにchroot脱獄を可能としているようです。(たぶん)
これを実装からみてみようと思います。外れているかもしれません。タスク構造他にはルートディレクトリ情報のrootとカレントディレクトリ情報のpwdのメンバーを有しています。例えば/aaa/bbb/ccc/dddでchroot cccとしたといたします。fs->rootはccc、fs->pwdはcccとなっています。(fs->pwdはchrootコマンドが設定していると思いますが・・・)
struct task_struct { ・・・・ struct fs_struct *fs; ・・・・ } struct fs_struct { atomic_t count; rwlock_t lock; int umask; struct path root, pwd; };パスのルックアップにおいて、絶対パスでしてされれば、ルートをfs->rootで設定されているパスを起点に、相対パスで指定されればfs->pwdを基点にして探査していきます。たとえばcd ..とすると、fs->pwdから親へ辿ってそれを返すことになります。
通常はfs->root配下にfs->pwdは存在することになりますが、chrootでfs->root配下以外にfs->pwdを設定することで、chroot牢獄をエスケープしようというわけです。
カーネルのchrootシステムコールはfs->rootを書き換えるだけです。従ってCでchrootシステムコールをコールしてcd..とする一連のプログラムを作成すればいいわけです。そのようなプロセスをchrootされているルート(ccc)から起動すると、子プロセスはfs->pwdを引き継ぎます。そうすると、fs->rootがddd、fs->pwdがcccとなるプロセスを作成することが可能となります。
SYSCALL_DEFINE1(chroot, const char __user *, filename) { ・・・・ set_fs_root(current->fs, &path); ・・・・ return error; } void set_fs_root(struct fs_struct *fs, struct path *path) { struct path old_root; write_lock(&fs->lock); old_root = fs->root; fs->root = *path; path_get(path); write_unlock(&fs->lock); if (old_root.dentry) path_put(&old_root); }あとは順にcd ..をしていきます。パス探索ではcd ..毎に、それがfs->rootでないかチェックします。もしfs->rootであればそこでお終いです。しかしfs->pwdがfs->rootの上だとcd ..としてfs->rootにマッチすることはありません。従って最終的に本来のルートにたどり着いたのちbashを起動することで、まさにchroot脱獄です。ただし、fs->rootはdddのままですので、このシェル上で絶対パスを指定すると、牢獄に逆戻り。ってことになってしまいま・・・す?
p/s
fs->pwdがコンポーネントした保持していないのに、なぜフルパスで表示されるのだろうか?と思って調べると、どうやら__d_pathをコールすることでfs->pwdから親へと遡って表示させているようでした。またそれゆえにchroot脱獄を可能としているようです。(たぶん)
char *__d_path(const struct path *path, struct path *root, char *buffer, int buflen) { ・・・・ for (;;) { struct dentry * parent; if (dentry == root->dentry && vfsmnt == root->mnt) break; マウントルートまたはルート(fs->root)のチェック if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { マウントルートの親が自分自身ってことはシステムルートってこと if (vfsmnt->mnt_parent == vfsmnt) { goto global_root; } マウントポイントポイントを次に探査対象とする dentry = vfsmnt->mnt_mountpoint; vfsmnt = vfsmnt->mnt_parent; continue; } 探査対象の親を取得 parent = dentry->d_parent; prefetch(parent); if ((prepend_name(&end, &buflen, &dentry->d_name) != 0) || (prepend(&end, &buflen, "/", 1) != 0)) goto Elong; retval = end; 親のdentryで再探索 dentry = parent; } ・・・・