はじめに
小ネタです。chroot
の動作を見るのに手を動かしてみた内容を書いたのが今回の記事です。
chrootはプロセスのルートディレクトリを変更するコマンド、関数、システムコールです。動かしてみてchrootの雰囲気がわかることが目標です。
Alpine LinuxのDockerイメージでglibcで動くプログラム(bash)を動かしてみる
Alpine Linuxはmusl libcが使われています。これからそこへglibcをインストールしてそれに依存しているbashを動かすという意味のないことをやります。何かプログラムを動かす時に、現在の環境を汚したくない時に使えるのではないでしょうか*1?
ではさっそく、Alpineのコンテナの中に入ります*2。
$ docker run -it alpine /bin/sh / #
chrootに必要なディレクリトリを準備します。
/ # mkdir /root/newroot / # cd /root/newroot ~/newroot # mkdir -p usr/bin/ bin dev proc root etc ~/newroot # ls bin dev etc proc root usr
glibcの展開に必要なツールをインストールします。
~/newroot # apk add libarchive-tools (1/7) Installing libacl (2.2.53-r0) (2/7) Installing libbz2 (1.0.8-r1) ... OK: 8 MiB in 21 packages
glibcをダウンロードして展開します。
~/newroot # wget https://www.archlinux.org/packages/core/x86_64/glibc/download/ -O - | bsdtar xfJ - Connecting to www.archlinux.org (138.201.81.199:443) ... written to stdout ~/newroot # ls usr/lib/ Mcrt1.o ld-linux-x86-64.so.2 libc.a libdl.so.2 libmvec.so libnss_dns-2.30.so libpthread.a libthread_db-1.0.so Scrt1.o libBrokenLocale-2.30.so libc.so libg.a libmvec.so.1 libnss_dns.so libpthread.so libthread_db.so audit libBrokenLocale.a libc.so.6 libm-2.30.a libmvec_nonshared.a libnss_dns.so.2 libpthread.so.0 libthread_db.so.1 crt1.o libBrokenLocale.so libc_nonshared.a libm-2.30.so libnsl-2.30.so libnss_files-2.30.so libresolv-2.30.so libutil-2.30.so crti.o libBrokenLocale.so.1 libcrypt-2.30.so libm.a libnsl.so.1 libnss_files.so libresolv.a libutil.a crtn.o libSegFault.so libcrypt.a libm.so libnss_compat-2.30.so libnss_files.so.2 libresolv.so libutil.so gconv libanl-2.30.so libcrypt.so libm.so.6 libnss_compat.so libnss_hesiod-2.30.so libresolv.so.2 libutil.so.1 gcrt1.o libanl.a libcrypt.so.1 libmcheck.a libnss_compat.so.2 libnss_hesiod.so librt-2.30.so locale getconf libanl.so libdl-2.30.so libmemusage.so libnss_db-2.30.so libnss_hesiod.so.2 librt.a rcrt1.o grcrt1.o libanl.so.1 libdl.a libmvec-2.30.so libnss_db.so libpcprofile.so librt.so systemd ld-2.30.so libc-2.30.so libdl.so libmvec.a libnss_db.so.2 libpthread-2.30.so librt.so.1 tmpfiles.d
chrootに必要なファイルを準備します。
~/newroot # cp /bin/busybox bin/ ~/newroot # cp -r /lib lib ~/newroot # ln -s /bin/busybox bin/sh ~/newroot # ln -s /bin/busybox bin/ln ~/newroot # cp /etc/resolv.conf etc/
chrootを実行します。
~/newroot # chroot . / #
ここでchrootを実行した「今動かしているシェル」のルートが変わります。今までの「必要なディレクトリを用意」などはchrootをしてルートディレクトリが/root/newroot
に変わったあとのシェルにとって必要なディレクトリという意味でした。
ls
など必要なコマンドはbusyboxを使います。busybox
はmusl libcに依存しています。
/ # ls usr/bin/ /bin/sh: ls: not found / # ln -s /bin/busybox /bin/ls / # ls bin dev etc lib proc root usr var
上のディレクトリへcd
してみます。
/ # cd .. / #
いけません。ちゃんとrootディレクトリが変わっています。
ではglibcに依存しているbashを入れてみます。bash実行に必要なシンボリックリンクを作って、いったんexit
でchrootから抜けます。
/ # ln -s /usr/lib /lib64 / # exit
bash本体と依存するライブラリを取ってきます。
~/newroot # wget https://www.archlinux.org/packages/core/x86_64/bash/download/ -O - | bsdtar xfJ - ~/newroot # wget https://www.archlinux.org/packages/core/x86_64/readline/download/ -O - | bsdtar xfJ - ~/newroot # wget https://www.archlinux.org/packages/core/x86_64/ncurses/download/ -O - | bsdtar xfJ -
chroot
でbashを指定して実行してみます。
~/newroot # chroot . /usr/bin/bash [I have no name!@eeb2b9133148 /]#
bashが動きました*3。ちなみにapk add bash
でbashをインストールするのと違うことは、bashの動作に必要なライブラリなどの配置をapk
がやってくれるのに対して、今回は自分で用意したところですね。
chroot システムコール
chrootのシステムコールを追ってみると、current
が持つfs
のルートを変更するというものなようです。current
の型はタスク構造体です。現在のプロセスを表しています。そのルートを変えたということです。
set_fs_root(current->fs, &path);
/* * Replace the fs->{rootmnt,root} with {mnt,dentry}. Put the old values. * It can block. */ 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); }