initramfsがrootfsを新しいルートで上書きする必要があるのはなぜですか?

initramfsがrootfsを新しいルートで上書きする必要があるのはなぜですか?

私は読んだinitramfsに関するLinuxドキュメントそしてソースコードswitch_root

文書には次のように記載されています。

別のルートデバイスに切り替えるときは、initrdivov_rootを実行してRAMディスクを取り外します。しかし、initramfsはrootfsです。 ivot_root rootfsを使用またはマウント解除できません。代わりに、rootfsのすべてのエントリを削除してスペースを解放してください(-xdev / -exec rm '{}' ';'を探す)。新しい root で rootfs を上書きする (cd /newmount; mount --move ./; chroot .), stdin/stdout/stderr を新しい /dev/console に接続し、新しい init を実行します。

そしてswitch_root次のことをします:

if (chdir(newroot)) {
    warn(_("failed to change directory to %s"), newroot);
    return -1;
}

...

if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) {
    close(cfd);
    warn(_("failed to mount moving %s to /"), newroot);
    return -1;
}

...

if (chroot(".")) {
    close(cfd);
    warn(_("failed to change root"));
    return -1;
}

なぜマウントポイントを別の場所に移動するのですか/
new_rootのchrootだけでは不十分な理由は何ですか?

ベストアンサー1

編集する:編集していただいた@timothy-baldwinに感謝します。

new_rootハイパーマウントは/マウントされた名前空間のルートディレクトリを変更し、ハイパーマウントなしでルートを変更すると/システムが環境になりますchroot(ルートディレクトリはマウントされた名前空間のルートディレクトリと一致しません)。

これにより、次のようないくつかの問題が発生する可能性があります。

1. chroot内では、ユーザーの名前空間の作成は許可されません。

によると、man 2 unsharechroot環境内ではunshareユーザーの名前空間を取得する操作が失敗します。EPERM

EPERM (since Linux 3.9)
      CLONE_NEWUSER was specified in flags and the caller is in a  chroot  environment
      (i.e., the  caller's root directory does not match the root directory of the
      mount namespace in which it resides).
$ unshare -U
unshare: unshare failed: Operation not permitted

2.マウントネームスペースを入力すると、ルートディレクトリがネームスペースのルートディレクトリに設定されます。

マウントネームスペースを入力すると、プロセスのルートがマウントネームスペースのルートに設定されるため、マウントsetnsネームスペースで操作を実行すると、ルートがrootfsディレクトリに設定されます。

$ nsenter -m/proc/self/ns/mnt /bin/sh
$ ls -ld /new_root
new_root

new_rootディレクトリが私のchrootの外にあることがわかります。

上にマウントしても、/chroot エスケープは実際には防止されません。

rootユーザーは、umountこのディレクトリにマウントネームスペース()を再入力しsetnsてrootfsを表示できます。

#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>

int main() {
    int ns = open("/proc/self/ns/mnt", O_RDONLY);
    if (ns == -1) {
        perror("open");
        goto out;
    }

    if (umount2("/", MNT_DETACH)) {
        perror("umount2");
        goto out;
    }

    if (setns(ns, CLONE_NEWNS)) {
        perror("setns");
        goto out;
    }

    char *a[] = { "/bin/sh", NULL };
    char *e[] = { NULL };
    execve(a[0], a, e);

    perror("execve");

out:
    return 1;
}
$ gcc -o main main.c
$ unshare -m ./main
/ # ls -d new_root
new_root
/ # mount -t proc proc /proc
/ # cat /proc/mounts
none / rootfs rw 0 0
proc /proc proc rw,relatime 0 0

chroot の逸脱を防ぐには、new_rootover をインストールする必要があります。/

最小限のinitramfsを作成し、switch_rootバイナリを次のシェルスクリプトに置き換えてシェルを取得しました。

#!/bin/sh

exec /bin/sh

/bin/shまた、initramfs内の静的リンクに変更されましたbusybox

次のコードをコンパイルして静的にリンクします。

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    int fd = open(".", O_RDONLY | O_CLOEXEC);
    if (fd < 0) {
        perror("open");
        goto out0;
    }

    if (chroot("tmp")) {
        perror("chroot");
        goto out1;
    }

    if (fchdir(fd)) {
        perror("fchdir");
        goto out1;
    }

    if (chdir("..")) {
        perror("chdir");
        goto out1;
    }

    char *const argvp[] = { "sh", NULL };
    char *const envp[] = { NULL };
    execve("bin/sh", argvp, envp);

    perror("execve");

out1:
    close(fd);

out0:
    return  1;

}

私の実際のルートファイルシステムのルートディレクトリに/escape

switch_rootこれが起こる前に再起動してシェルを入手してください。

ルートを過度にインストールする必要はありません。

$ mount --move proc new_root/proc
$ mount --move dev new_root/dev
$ mount --move sys new_root/sys
$ mount --move run new_root/run
$ exec chroot new_root
$ ./escape
$ ls -d new_root
new_root

私はchrootから脱出しました。

過負荷ルートを含む

$ mount --move proc new_root/proc
$ mount --move dev new_root/dev
$ mount --move sys new_root/sys
$ mount --move run new_root/run
$ cd new_root
$ mount --move . /
$ exec chroot .
$ ./escape
$ ls -d new_root
ls: cannot access 'new_root': No such file or directory

私はchrootから抜け出すことはできません。

おすすめ記事