文件訪問權限:更改用戶ID
本文來探討一下通過更改用戶ID來獲取合適的文件訪問權限。由於更改組ID的規則與用戶ID相同,我們在這裏只探討用戶ID。
紙上得來終覺淺
先了解以下幾個基本知識:
- 用戶ID包括:實際用戶ID、有效用戶ID、保存的設置用戶ID。其中保存的設置用戶ID由exec函數保存。
- 實際用戶ID標識我們究竟是誰,該字段在登錄時取自口令文件中的登錄項。通常,在一個登錄會話期間該值不會改變,但root用戶進程有方法改變它。
- 有效用戶ID決定了我們的文件訪問權限。
- 保存的設置用戶ID在執行一個程序時包含了有效用戶ID的副本。
- 通常,有效用戶ID等於實際用戶ID。
- 當執行一個程序文件時,進程的有效用戶ID通常就是實際用戶ID。但是可以在文件模式字(st_mode
更改這3個用戶ID的不同方法總結如下:
ID | exec | setuid(uid) | ||
設置用戶ID位關閉 | 設置用戶ID位打開 | 超級用戶 | 非特權用戶 | |
實際用戶ID | 不變 | 不變 | 設為uid | 不變 |
有效用戶ID | 不變 | 設置為程序文件的用戶ID | 設為uid | 設為uid |
保存的設置用戶ID | 從有效用戶ID復制 | 從有效用戶ID復制 | 設為uid | 不變 |
關於以上幾點,我們參考一下《Unix高級環境編程》一書中給出的一個例子:
Unix系統程序/usr/bin/passwd是一個設置用戶ID程序,該文件的所有者是root用戶,而且設置了該文件的設置用戶ID位。那麽當這個程序文件由一個進程執行時,該進程具有root用戶權限。不管執行此文件的進程的實際用戶ID是什麽,都會是這樣,因為該進程的權限不是由實際用戶ID決定的,而是由有效用戶ID決定的。系統程序/usr/bin/passwd應該能將用戶的新口令寫入口令文件(一般是/etc/passwd 或 /etc/shadow)中,而只有root用戶才具有對該文件的寫權限,所以需要使用設置用戶ID功能。因為運行設置用戶ID程序的進程通常會得到額外的權限,所以編寫這種程序時要特別謹慎。
絕知此事要躬行
假設一臺Linux主機上有2個用戶:Jim(實際用戶ID為1000)和Lucy(實際用戶ID為1001)。
Jim在自己的主目錄下有一個文件test.txt,只有自己才能讀寫。該文件的訪問權限如下所示:
[email protected]:~$ ls -l test.txt
-rw------- 1 Jim Jim 13 Sep 13 17:07 /home/Jim/test.txt
Lucy想查看該文件,卻沒有相應的權限,因此會被拒絕:
[email protected]:~$ cat /home/Jim/test.txt
cat: /home/Jim/test.txt: Permission denied
[email protected]:~$ sudo cat /home/Jim/test.txt
[sudo] password for Lucy:
hello world!
結果Lucy使用root權限強行查看了Jim的test.txt文件。這就沒什麽好說的了。不過,我們還是建議,在Linux上能不用root權限就不用root權限,用多了可能會傷及無辜。
我們自己的數據文件要由我們自己來保護。Jim可以編寫一個設置用戶ID程序,Lucy通過執行這個程序,就可以查看Jim的test.txt文件。代碼如下:
1 #include <stdio.h> 2 #include <unistd.h> 3 4 #define BUF_LEN (256) 5 6 int main(void) 7 { 8 FILE * fp = NULL; 9 char buf[BUF_LEN] = {0}; 10 11 uid_t real_uid, effect_uid, saved_uid; 12 13 if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) { 14 perror("getresuid"); 15 return -1; 16 } 17 18 printf("===== before setuid =====\n"); 19 20 printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid); 21 22 /* 23 * the first important thing this program does is reduce its privilege by using setuid function. 24 * otherwise we can not protect our data file. 25 */ 26 27 // we reduce this program‘s privilege to real_uid. 28 if (setuid(real_uid) == -1) { 29 perror("setuid"); 30 return -1; 31 } 32 33 printf("====== after setuid with real_uid =====\n"); 34 35 if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) { 36 perror("getresuid"); 37 return -1; 38 } 39 40 printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid); 41 42 fp = fopen("/home/Jim/test.txt", "r"); 43 if (fp == NULL) { 44 printf("\n##### error #####\n"); 45 perror("fopen"); 46 printf("##### error #####\n\n"); 47 } 48 49 // now we raise this program‘s privilege to saved_uid. 50 if (setuid(saved_uid) == -1) { 51 perror("setuid"); 52 return -1; 53 } 54 55 printf("====== after setuid with saved_uid =====\n"); 56 57 if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) { 58 perror("getresuid"); 59 return -1; 60 } 61 62 printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid); 63 64 fp = fopen("/home/Jim/test.txt", "r"); 65 if (fp == NULL) { 66 printf("\n##### error #####\n"); 67 perror("fopen"); 68 printf("##### error #####\n\n"); 69 70 return -1; 71 } 72 73 printf("\n----- file read -----\n"); 74 while (fgets(buf, BUF_LEN, fp)) 75 printf("%s", buf); 76 77 if (feof(fp)) 78 printf("\nfile reach end!\n"); 79 else 80 ferror(fp); 81 printf("----- file close -----\n\n"); 82 83 fclose(fp); 84 85 // reduce this program‘s privilege by setting effect_uid to saved_uid 86 if (seteuid(saved_uid) == -1) { 87 perror("seteuid"); 88 return -1; 89 } 90 91 printf("====== after seteuid with saved_uid =====\n"); 92 93 if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) { 94 perror("getresuid"); 95 return -1; 96 } 97 98 printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid); 99 100 fp = fopen("/home/Jim/test.txt", "r"); 101 if (fp == NULL) { 102 printf("\n##### error #####\n"); 103 perror("fopen"); 104 printf("##### error #####\n\n"); 105 } else { 106 printf("file opened!\n\n"); 107 fclose(fp); 108 } 109 110 // reduce this program‘s privilege by setting effect_uid to real_uid 111 if (seteuid(real_uid) == -1) { 112 perror("seteuid"); 113 return -1; 114 } 115 116 printf("====== after seteuid with real_uid =====\n"); 117 118 if (getresuid(&real_uid, &effect_uid, &saved_uid) == -1) { 119 perror("getresuid"); 120 return -1; 121 } 122 123 printf("real_uid = %d, effect_uid = %d, saved_uid = %d\n\n", real_uid, effect_uid, saved_uid); 124 125 fp = fopen("/home/Jim/test.txt", "r"); 126 if (fp == NULL) { 127 printf("\n##### error #####\n"); 128 perror("fopen"); 129 printf("##### error #####\n\n"); 130 } else { 131 printf("file opened!\n\n"); 132 fclose(fp); 133 } 134 135 return 0; 136 }
編譯該程序的時候要加上_GNU_SOURCE標誌(用於getresuid函數),編譯完成之後還要打開該程序的設置用戶ID標誌位。
[email protected]:~$ gcc uid_test.c -o uid_test -D _GNU_SOURCE
[email protected]:~$ chmod u+s uid_test
[email protected]:~$ ls -l uid_test
-rwsrwxr-x 1 Jim Jim 7726 Sep 14 17:19 /home/Jim/uid_test
從上面的命令輸出中可以看到,uid_test程序的設置用戶ID標誌位已經打開。Lucy可以使用這個程序來查看Jim的test.txt文件。
[email protected]:~$ /home/Jim/uid_test
===== before setuid =====
real_uid = 1001, effect_uid = 1000, saved_uid = 1000
====== after setuid with real_uid =====
real_uid = 1001, effect_uid = 1001, saved_uid = 1000
##### error #####
fopen: Permission denied
##### error #####
====== after setuid with saved_uid =====
real_uid = 1001, effect_uid = 1000, saved_uid = 1000
----- file read -----
hello world!
file reach end!
----- file close -----
====== after seteuid with saved_uid =====
real_uid = 1001, effect_uid = 1000, saved_uid = 1000
file opened!
====== after seteuid with real_uid =====
real_uid = 1001, effect_uid = 1001, saved_uid = 1000
##### error #####
fopen: Permission denied
##### error #####
[email protected]:~$
從上面的輸出中可以看出,通過改變用戶ID可以控制程序的訪問權限。
另外註意
setuid函數與seteuid函數的區別:
相同之處:對於一個非特權用戶來說,兩者都可以將進程的有效用戶ID設置為其實際用戶ID或其保存的設置用戶ID。
不同之處:對於一個特權用戶來說,setuid(uid)函數將實際用戶ID、有效用戶ID以及保存的設置用戶ID設為uid;而seteuid(uid)函數只將進程的有效用戶ID設為uid。
還是那句話:因為運行設置用戶ID程序的進程通常會得到額外的權限,所以編寫這種程序時要特別謹慎。
文件訪問權限:更改用戶ID