1. 程式人生 > 其它 >Linux之25——chroot 命令

Linux之25——chroot 命令

chroot,即 change root directory (更改 root 目錄)。在 linux 系統中,系統預設的目錄結構都是以 /,即以根 (root) 開始的。而在使用 chroot 之後,系統的目錄結構將以指定的位置作為 / 位置。

基本語法

chroot NEWROOT [COMMAND [ARG]...]

具體用法請參考本文的 demo。

為什麼要使用 chroot 命令

增加了系統的安全性,限制了使用者的權力:
在經過 chroot 之後,在新根下將訪問不到舊系統的根目錄結構和檔案,這樣就增強了系統的安全性。一般會在使用者登入前應用 chroot,把使用者的訪問能力控制在一定的範圍之內。

建立一個與原系統隔離的系統目錄結構,方便使用者的開發:
使用 chroot 後,系統讀取的是新根下的目錄和檔案,這是一個與原系統根下檔案不相關的目錄結構。在這個新的環境中,可以用來測試軟體的靜態編譯以及一些與系統不相關的獨立開發。

切換系統的根目錄位置,引導 Linux 系統啟動以及急救系統等:
chroot 的作用就是切換系統的根位置,而這個作用最為明顯的是在系統初始引導磁碟的處理過程中使用,從初始 RAM 磁碟 (initrd) 切換系統的根位置並執行真正的 init,本文的最後一個 demo 會詳細的介紹這種用法。

通過 chroot 執行 busybox 工具

busybox 包含了豐富的工具,我們可以把這些工具放置在一個目錄下,然後通過 chroot 構造出一個 mini 系統。簡單起見我們直接使用 docker 的 busybox 映象打包的檔案系統。先在當前目錄下建立一個目錄 rootfs:

$ mkdir rootfs

然後把 busybox 映象中的檔案釋放到這個目錄中:

$ (docker export $(docker create busybox) | tar -C rootfs -xvf -)

通過 ls 命令檢視 rootfs 資料夾下的內容:

$ ls rootfs

萬事俱備,讓我們開始吧!

執行 chroot 後的 ls 命令

$ sudo chroot rootfs /bin/ls

雖然輸出結果與剛才執行的 ls rootfs 命令形同,但是這次執行的命令卻是 rootfs/bin/ls。

執行 chroot 後的 pwd 命令

$ sudo chroot
rootfs /bin/pwd

哈,pwd 命令真把 rootfs 目錄當根目錄了!

不帶命令執行 chroot

$ sudo chroot rootfs

這次出錯了,因為找不到 /bin/bash。我們知道 busybox 中是不包含 bash 的,但是 chroot 命令為什麼會找 bash 命令呢? 原來,如果不給 chroot 指定執行的命令,預設它會執行 '${SHELL} -i',而我的系統中 ${SHELL} 為 /bin/bash。
既然 busybox 中沒有 bash,我們只好指定 /bin/sh 來執行 shell 了。

$ sudo chroot rootfs /bin/sh

執行 sh 是沒有問題的,並且我們打印出了當前程序的 PID。

檢查程式是否執行在 chroot 環境下

雖然我們做了好幾個實驗,但是肯定會有朋友心存疑問,怎麼能證明我們執行的命令就是在 chroot 目錄後的路徑中呢?
其實,我們可以通過 /proc 目錄下的檔案檢查程序的中的根目錄,比如我們可以通過下面的程式碼檢查上面執行的 /bin/sh 命令的根目錄(請在另外一個 shell 中執行):

$ pid=$(pidof -s sh)
$ sudo ls -ld /proc/$pid/root

輸出中的內容明確的指出 PID 為 46644 的程序的根目錄被對映到了 /tmp/rootfs 目錄。

通過程式碼理解 chroot 命令

下面我們嘗試自己實現一個 chroot 程式,程式碼中涉及到兩個函式,分別是 chroot() 函式和 chdir() 函式,其實真正的 chroot 命令也是通過呼叫它們實現的:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
if(argc<2){
printf(
"Usage: chroot NEWROOT [COMMAND...] \n");
return
1;
}

</span><span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">chroot</span>(argv[<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">])) {
    perror(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">chroot</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    return </span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
}

</span><span style="color: rgba(0, 0, 255, 1)">if</span>(chdir(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)) {
    perror(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">chdir</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    return </span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
}

</span><span style="color: rgba(0, 0, 255, 1)">if</span>(argc == <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> hardcode /bin/sh for my busybox tools.</span>
    argv[<span style="color: rgba(128, 0, 128, 1)">0</span>] = (<span style="color: rgba(0, 0, 255, 1)">char</span> *)<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/bin/sh</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;

    argv[</span><span style="color: rgba(128, 0, 128, 1)">1</span>] = (<span style="color: rgba(0, 0, 255, 1)">char</span> *) <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-i</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    argv[</span><span style="color: rgba(128, 0, 128, 1)">2</span>] =<span style="color: rgba(0, 0, 0, 1)"> NULL;
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    argv </span>+= <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">;
}

execvp (argv[</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">], argv);
printf(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">chroot: cannot run command `%s`\n</span><span style="color: rgba(128, 0, 0, 1)">"</span>, *<span style="color: rgba(0, 0, 0, 1)">argv);

return </span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">;

}

把上面的程式碼儲存到檔案 mychroot.c 檔案中,並執行下面的命令進行編譯:

$ gcc -Wall mychroot.c -o mychroot

mychroot 的用法和 chroot 基本相同:

$ sudo ./mychroot ./rootfs

特別之處是我們的 mychroot 在沒有傳遞命令的情況下執行了 /bin/sh,原因當然是為了支援我們的 busybox 工具集,筆者在程式碼中 hardcode 了預設的 shell:

argv[0] = (char *)"/bin/sh";

從程式碼中我們也可以看到,實現 chroot 命令的核心邏輯其實並不複雜。

例項:通過 chroot 重新設定 root 密碼

忘記了 root 密碼該怎麼辦?接下來的 demo 將演示如何通過 chroot 命令重新設定 centos7 中被忘記了的 root 密碼。
systemd 的管理機制中,rescure 模式和 emeryency 模式是無法直接取得 root 許可權的,需要使用 root 密碼才能進入 rescure 和 emeryency 環境。所以我們需要通過其他方式來設定 root 密碼。我們可以為核心的啟動指定 "rd.break" 引數,從而讓系統在啟動的早期停下來,此時我們可以通過使用 root 許可權並結合 chroot 命令完成設定 root 密碼的操作。下面我們一起來看具體的操作過程。

在系統啟動過程中進入開機選單時按下字母鍵 e 程序開機選單的編輯模式:

這就是系統的開機選單,按下 e 後進入編輯介面:

找到以 "linux16 /vmlinuz-" 開頭的行。如果預設沒有看到該行,需要按向下鍵把它滾動出來。
然後定位到該行結尾處,輸入一個空格和字串 "rd.break",如下圖所示:

接著按下 ctrl + x 以該設定繼續啟動,啟動過程中作業系統會停下來,這是系統啟動過程中的一個非常早的時間點:

所以系統的根目錄還掛載在 RAM disk 上(就是記憶體中的一個檔案系統),我們可以通過 mount 命令檢查系統當前掛載的檔案系統,下面是我們比較關心的兩條:

上圖中 mount 命令輸出的第一行說明此時的根目錄在一個 RAM disk 中, 即 rootfs。
圖中輸出的第二行說明我們的檔案系統此時被掛載到了 /sysroot 目錄,並且是隻讀的模式:

/dev/mapper/centos-root on /sysroot type xfs (ro,relatime,attr2,inode64,noquota)

而在我們正常登陸系統的情況下,系統根目錄的掛載情況如下:

/dev/mapper/centos-root on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

該時間點的最大優勢是我們具有 root 許可權!所以讓我們開始設定新的 root 密碼吧。

先通過下面的命令把 /sysroot 重新掛載為可讀寫的模式:

switch_root:/# mount -o remount,rw /sysroot

然後用下面 chroot 命令把根目錄切換到我們原來的環境中:

switch_root:/# chroot /sysroot

此時可以理解為:我們以 root 許可權登入了原來的系統,修改密碼就很容易了!用下面的命令為 root 使用者設定新的密碼:

sh-4.2# echo "new_root_pw" | passwd --stdin root

接下來還要處理 SELinux 相關的問題。由於當前的環境中 SELinux 並未啟動,所以我們對檔案的修改可能造成檔案的 context 不正確。為了確保開機時重新設定 SELinux context,必須在根目錄下新增隱藏檔案 .autorelabel:

sh-4.2# touch /.autorelabel

最後從 chroot 中退出,並重啟系統:

sh-4.2# exit
switch_root:/# reboot

重新進入登陸介面時就可以使用剛才設定的密碼以 root 登陸了!

總結

chroot 是一個很有意思的命令,我們可以用它來簡單的實現檔案系統的隔離。但在一個容器技術繁榮的時代,用 chroot 來進行資源的隔離實在是 low 了點。所以 chroot 的主要用途還是集中在系統救援、維護等一些特殊的場景中。

參考:
理解 chroot
Linux – RedHat7 / CentOS 7 忘記root密碼修改