linux 命令分析之 chroot 的原理

longyu_wlz發表於2020-10-24

chroot 的功能

chroot 可以用來切換當前程式的【根目錄】,它能夠將當前程式能夠訪問的目錄樹結構限制
在某個子目錄中,同時由於當前程式建立的子程式將會繼承父程式的根目錄結構,所以子進
程也隨之被限定。

chroot 命令的原理

通過 strace 來跟蹤一次 chroot 命令執行過程來研究其程式碼執行過程。

這裡我刪除了與這個問題沒有太大關係的一些輸出,重要的系統呼叫資訊如下:

[root@localhost new_test]# strace chroot . sh
execve("/usr/sbin/chroot", ["chroot", ".", "sh"], 0x7fffbf1a9580 /* 28 vars */) = 0
..............................
chroot(".")                             = 0
chdir("/")                              = 0
execve("/usr/local/sbin/sh", ["sh"], 0x7fff5cd3d1a8 /* 28 vars */) = -1 ENOENT (沒有那個檔案或目錄)
execve("/usr/local/bin/sh", ["sh"], 0x7fff5cd3d1a8 /* 28 vars */) = -1 ENOENT (沒有那個檔案或目錄)
execve("/usr/sbin/sh", ["sh"], 0x7fff5cd3d1a8 /* 28 vars */) = -1 ENOENT (沒有那個檔案或目錄)
execve("/usr/bin/sh", ["sh"], 0x7fff5cd3d1a8 /* 28 vars */) = 0
.............................

上述過程可以總結為如下幾個步驟:

  1. execve 執行 chroot 程式
  2. chroot 系統呼叫切換當前命令的根目錄
  3. chdir 系統呼叫切換當前命令的工作目錄到新的根目錄
  4. 根據路徑搜尋預設 shell 的位置,使用 execve 進行執行

這裡它搜尋預設 shell 的路徑時有幾個備選路徑,搜尋順序如下:

  1. /usr/local/sbin/sh
  2. /usr/local/bin/sh
  3. /usr/sbin/sh
  4. /usr/bin/sh

從上面的捕獲到的 chroot 命令的系統呼叫可以看到 chroot 命令的核心其實就是呼叫
chroot 系統呼叫,這也就是其核心態的主要行為,這個行為並不是直接完成這項功能
的,它實際是通過一種間接的方式修改 task_struct 中的資料結構來達成的。

為什麼 chroot 要 chdir 到 “/” 目錄中呢?

細心的讀者也許會注意到 chroot 在執行了 chroot 系統呼叫後,又呼叫了 chdir 切換
【當前目錄】到【新的根目錄】的行為,這個行為是必不可少的!

chdir 系統呼叫將會修改當前程式 task_sturct fs 中的 pwd 欄位,這個欄位儲存了當前工作
目錄的 dentry 結構體。

核心程式碼如下:

	set_fs_pwd(current->fs, &path);

如果 chroot 命令不 chdir 到 “/” 目錄,那麼我們仍舊可以在 chroot 後的程式中通過訪問到
外界的目錄
,這樣就會出現非常好玩的情景。

不讓 chroot 執行 chdir 過程的測試

為了驗證這點,我進行了如下幾個嘗試。

1. 指定 --skip-chdir 引數

chroot 命令中有一個 –skip-chdir 引數,在根目錄中執行如下命令來使用它:

[root@localhost /]# /root/chroot --skip-chdir /root/chroot_environment/ sh
/root/chroot: option --skip-chdir only permitted if NEWROOT is old '/'

這裡報錯資訊表明 –skip-chdir 只在新的 root 目錄與老的根目錄相同時才被允許使用

2. 修改 chroot 命令原始碼

chroot 命令在 coreutils 包中,執行 sudo apt-get source coreutils 來下載,其源
碼在 src/chroot.c 中,對程式碼進行如下修改讓 chroot 不執行 chdir 操作:

#if 0
  if (! skip_chdir && chdir ("/"))
    die (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
#endif

終端中測試不執行 chdir 的 chroot

重新編譯後執行,在終端中執行如下命令:

[root@localhost /]# /chroot /root/chroot_environment sh
(unreachable)/ # ls
bin     chroot  etc     lib     media   opt     root    sbin    sys     usr
boot    dev     home    lib64   mnt     proc    run     srv     tmp     var
(unreachable)/ # chroot .
[root@localhost /]# ls
bin  boot  chroot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@localhost /]# exit
exit
(unreachable)/ # cd ./root
sh: cd: can't cd to ./root: No such file or directory
(unreachable)/ # cd /root
~ # cd ..
/ # ls
bin    dev    etc    lib64  proc   root   sys    usr    var
/ #

第一行命令切根後,由於沒有 chdir 到新的根目錄,cwd 中仍然儲存了舊的工作目錄的
dentry 資訊
,這樣我們仍舊能夠訪問到舊的根目錄下的所有檔案,第二行的 ls 命令的
輸出資訊證實了這點。

第三行命令繼續執行了 chroot,注意這裡的引數為 .. 表示當前目錄,這行命令執行後,又
切回了原來的根目錄。

第四行命令從第三行命令的 chroot 環境中退出,退出後嘗試 cd ./root,發現會報這個目錄
不存在的錯誤。

通過 strace 跟蹤這個 sh,發現它傳遞給 chdir 系統呼叫的命令並不是 ./root,而是 "
(unreachable)/root",這個目錄當然不存在嘍,就報了目錄不存在的錯誤。

相關的系統呼叫內容如下:

chdir("(unreachable)/root")             = -1 ENOENT (沒有那個檔案或目錄)
write(2, "sh: ", 4)                     = 4
write(2, "cd: ", 4)                     = 4
write(2, "can't cd to ./root: No such file"..., 45) = 45

單獨編寫了一個呼叫 chdir(“root”) 的程式碼編譯後執行測試,確認能夠 chdir,看來應該是 sh
進行了某種處理。

sh 中對當前目錄的處理

這裡新增的 (unreachable) 是 PS1 變數控制的,檢視 manual 確定這個字串實際上對
應的是當前的工作目錄。相關資訊如下:

\w     當前工作目錄

我當前 shell 的 PS1 設定如下:

(unreachable)/ # echo $PS1
\w \$

看來 sh 執行的時候應該是通過當前目錄的變數將我輸入的 ./ 展開了,這樣就有上面的
問題了。

chdir 到 /root 後切到新的根目錄,完成 chroot 的所有過程

第五行命令中 cd /root 成功,這是肯定的,因為當前程式的根目錄已經切換了,所以我們能
夠進入到這個 /root 目錄,而且這個目錄也是新的目錄。

第六行命令中 cd … 返回根目錄,然後執行 ls 命令,可以看到此時根目錄下的檔案已經變
了,直至這裡才完成了 chroot 的所有過程。

相關文章