上一篇我們使用了 stat 函數取得了 test.txt 檔案的相關屬性,這些屬性都儲存在一個叫 struct stat 的結構體中:
struct stat {
dev_t st_dev; /* 包含這個檔案的裝置 ID */
ino_t st_ino; /* inode 編号 */
mode_t st_mode; /* 通路權限 */
nlink_t st_nlink; /* 硬連結數量 */
uid_t st_uid; /* 使用者ID */
gid_t st_gid; /* 組ID */
dev_t st_rdev; /* 裝置ID */
off_t st_size; /* 檔案占用的位元組數 */
blksize_t st_blksize; /* 檔案系統塊大小 */
blkcnt_t st_blocks; /* 檔案占用了幾個512位元組 */
time_t st_atime; /* 最後通路時間 */
time_t st_mtime; /* 最後更改時間 */
time_t st_ctime; /* 最後狀态更改時間 */
本篇我們隻介紹 st_mode 字段。在上一篇中,我們得到的 st_mode 字段的10進制值是 33204. 記住這個值,待會我們要分析。
1 一堆 ID
- 實際使用者 ID、實際組 ID
- 有效使用者 ID、有效組 ID
- 設定使用者 ID、設定組 ID
1、實際使用者ID(uid)和實際使用者組ID(gid):辨別我是誰。也就是登入使用者的uid和gid,比如我的Linux以allen登入,在Linux運作的所有的指令的實際使用者ID都是allen的uid,實際使用者組ID都是allen的gid(可以用id指令檢視)。
2、有效使用者ID(euid)和有效使用者組ID(egid):程序用來決定我們對資源的通路權限。一般情況下,有效使用者ID等于實際使用者ID,有效使用者組ID等于實際使用者組ID。當設定-使用者-ID(SUID)位設定,則有效使用者ID等于檔案的所有者的uid,而不是實際使用者ID;同樣,如果設定了設定-使用者組-ID(SGID)位,則有效使用者組ID等于檔案所有者的gid,而不是實際使用者組ID。
例:
- 你在登入 linux 時輸入的帳号,就是實際使用者。比方說我登入的帳号是 allen,那麼實際使用者就是 allen.
- 當你以 sudo 執行指令時,比如 sudo rm text.txt,這時候 ,在執行這條指令的時候 ,實際使用者是 allen,有效使用者是 root。
- 假設目前實際使用者是 allen,你執行的檔案 a.out 的所有者是 david,這時候你在執行這條指令的時候 ,實際使用者是 allen,有效使用者也是 allen,如果 a.out 這個檔案設定使用者 ID 标志打開,那麼執行 a.out 的時候,實際使用者是 allen, 有效使用者是 david.
2 st_mode 的結構
st_mode 主要包含了 3 部分資訊:
- 15-12 位儲存檔案類型
- 11-9 位儲存執行檔案時設定的資訊
- 8-0 位儲存檔案通路權限
圖1 展示了 st_mode 各個位的結構。
圖1 st_mode 結構(圖中花括号裡的數字都是 2 進制)
2.1 黏着位(sticky)
要删除一個檔案,你不一定要有這個檔案的寫權限,但你一定要有這個檔案的上級目錄的寫權限。也就是說,你即使沒有一個檔案的寫權限,但你有這個檔案的上級目錄的寫權限,你也可以把這個檔案給删除,而如果沒有一個目錄的寫權限,也就不能在這個目錄下建立檔案。
如何才能使一個目錄既可以讓任何使用者寫入檔案,又不讓使用者删除這個目錄下他人的檔案,sticky就是能起到這個作用。sticky一般隻用在目錄上,用在檔案上起不到什麼作用。
在一個目錄上設了sticky位後,(如/home,權限為1777)所有的使用者都可以在這個目錄下建立檔案,但隻能删除自己建立的檔案(root除外),這就對所有使用者能寫的目錄下的使用者檔案啟到了保護的作用。
如果使用者對目錄有寫權限,則可以删除其中的檔案和子目錄,即使該使用者不是這些檔案的所有者,而且也沒有讀或寫許可。黏着位出現執行許可的位置上,用t表示,設定了該位後,其它使用者就不可以删除不屬于他的檔案和目錄。但是該目錄下的目錄不繼承該權限,要再設定才可使用。
普通檔案的sticky位會被linux核心忽略。
目錄的sticky位表示這個目錄裡的檔案隻能被owner和root删除 。
/tmp常被我們用來存放臨時檔案,是所有使用者。但是我們不希望别的使用者随随便便的就删除了自己的檔案,于是便有了黏着位,它的作用便是讓使用者隻能删除屬于自己的檔案。
$ ls -dl /tmp
drwxrwxrwt 15 root root ......... // 注意 other 使用者的權限位,x 變成了 t
那麼原來的執行标志x到哪裡去了呢? 系統是這樣規定的, 假如本來在該位上有x, 則這些特别标志 (suid, sgid, sticky) 顯示為小寫字母 (s, s, t). 否則, 顯示為大寫字母 (S, S, T) 。
另外:
chmod 777 abc
chmod
等價于
chmod 1777
3 執行個體分析
3.1 普通檔案執行個體
上一篇我們得到的 st_mode 10進制值是 33204,轉換成 8 進制後為 100664.
把它填到圖1中後,是這樣:
圖2 33204 填到 st_mode 中
根據圖2 我們可以得到如下資訊:
- 1000: 這是一個正常檔案
- 000: 執行時設定資訊為空,黏着位為 0
- 110-110-100: 使用者權限為
,組員權限為RW-
,其他人權限為RW-
R--
3.2 帶設定-使用者-ID标志的檔案分析
我們要分析的檔案是這樣的:
-rwsr-xr-x 1 root root 7612 11月 28 14:58 append
-rw-r--r-- 1 root root 6 11月 28 14:52 test.txt
append 是一個可執行檔案,它的作用是以追加的方式打開 test.txt,代碼如下:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd = open("test.txt", O_WRONLY | O_APPEND);
if (fd == -1) {
perror("open");
return -1;
}
printf("uid: %d\n", getuid());
printf("euid: %d\n", geteuid());
close(fd);
return 0;
}
append 檔案生成方式如下:
$ gcc append.c -o append
$ sudo chown root:root append
$ sudo chmod u+s append
目前實際使用者并不是 root,而是 allen.
一般來說,直接執行 ./append 會出現通路被禁止,因為 test.txt 檔案的所有者并不是 allen. 然而,這裡的會正常執行,結果顯示如下:
uid:1000
euid:0
即實際使用者 ID 是 allen, 有效使用者 id 是 root. 為什麼直接執行 ./append 後有效使用者 id 不是 allen,而變成 了 root? 這裡實際上是 append 檔案的 suid 位被置 1 了。
通過 stat 函數,我們得到的 append 的 st_mode 值為 35309. 轉換成 8 進制後為 104755. 将其填充到圖 1 的結構中後是這樣的:
圖3 填充 st_mode
分析圖3,可以知道:
- 1000:這是一個普通檔案
- 100:suid 為 1
- 111-101-101:使用者權限為
RWX
,組員權限為
R-X
,其他人權限為
R-X
是以這裡的 append 檔案在執行的時候,發現 suid 标志被打開,它會把有效使用者id (euid) 設定成 append 檔案的所有者(root)。
4 一些常用的宏
前面我們通過手工分析 st_mode 字段,實際上是很不友善的。實際寫程式,你可以使用 st_mode & 掩碼來得到 st_mode 中特定的部分。比如:
- st_mode & 0170000 : 得到檔案類型
- st_mode & 0007000 : 得到執行檔案時設定資訊
- st_mode & 0000777 : 得到權限位
-
st_mode & 00100: 判斷所有者是否可執行
……
如果在程式中真這麼寫,估計很少有人願意去看。實際上,可以使用 linux 預定義的一些宏來代替這些生硬的數字。這些宏定義在 sys/stat.h 頭檔案中。
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
總結
- 實際使用者 ID,實際使用者組 ID
- 有效使用者 ID,有效使用者組 ID
- 設定使用者 ID,設定使用者組 ID
- st_mode 字段結構(檔案類型-執行時檔案設定資訊-權限位)
- 黏着位