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コマンドが設定していると思いますが・・・)
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;
       }
・・・・


最終更新 2010/03/24 14:43:18 - north
(2010/03/23 15:34:46 作成)


検索

アクセス数
3676070
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。