setuid函數解析,setuid函數
在討論這個setuid函數之前,我們首先要瞭解的一個東西就是核心為每個進程維護的三個UID值。這三個UID分別是實際使用者ID(real uid)、有效使用者ID(effective uid)、儲存的設定使用者ID(saved set-user-ID)。
首先說這個實際使用者ID,就是我們當前以哪個使用者登入了,我們啟動並執行程式的實際使用者ID就是這個使用者的ID。有效使用者ID就是當前進程是以哪個使用者ID來啟動並執行,一般情況下是實際使用者ID,如果可執行檔具有了SUID許可權,那麼它的有效使用者ID就是可執行檔的擁有者。儲存的設定使用者ID就是有效使用者ID的一個副本,與SUID許可權有關。
關於那個SUID,最經典的例子莫過於passwd命令。passwd這個可執行檔的所有者是root,但是其他使用者對於它也有執行許可權,並且它自身具有SUID許可權。那麼當其他使用者來執行passwd這個可執行檔的時候,產生的進程的就是以root使用者的ID來啟動並執行。如所示:
這個passwd的所有者是root,並且在許可權那一欄中s代表的是SUID許可權。我以普通使用者michael來運行這個命令,產生的進程如下:
大家可以看到,倒數第二行這個進程其命令是passwd,但是其USER卻是root。這就是SUID許可權。
接下來,我們開始扯正題,setuid函數。這是setuid函數的聲明和傳回值:
如果成功返回0,如果失敗返回-1,並將errno設定為合適的值。
關於這個函數的功能,APUE(《UNIX環境進階編程》,下面的APUE為相同的意思,不再贅述)上面是這樣寫的:
這樣光講概念可能有點乾巴巴了,下面我來舉幾個分別說明一下:
開始講之前,我先驗證一下我的電腦是支援"儲存的設定使用者ID"這個功能的,在電腦上運行以下代碼:
1 #include<stdio.h> 2 #include<string.h> 3 #include<stdlib.h> 4 #include<stdarg.h> 5 #include<errno.h> 6 #include<unistd.h> 7 #define BUFSIZE 512 8 void err_exit(char *fmt,...); 9 int main(int argc,char *argv[])10 {11 if(sysconf(_SC_SAVED_IDS) > 0)12 printf("_POSIX_SAVED_IDS effective!\n");13 14 return 0;15 }
程式的運行結果如下:
首先說一下我用到的使用者,一共使用了三個使用者,root(UID=0),michael(UID=500),xiaojie(UID=501)如所示:
先講第一點,如果進程具有超級使用者特權,那麼這三個UID都被設定成了uid(setuid函數的參數)的值。比如下面這段代碼:
1 #include<sys/types.h> 2 #include<unistd.h> 3 #include<stdio.h> 4 int main(int argc,char *argv[]) 5 { 6 uid_t ruid,euid,suid; 7 //這三個uid分別是實際使用者ID,有效使用者ID,儲存的設定使用者ID 8 9 setuid(500);10 getresuid(&ruid,&euid,&suid);11 printf("real:%d\teffective:%d\tset-user-id:%d\n",ruid,euid,suid);12 13 return 0;14 }
我用root使用者來運行這個程式,那麼剛開始這個進程的有效使用者ID為0,然後這個進程中調用setuid(500),這三個UID的值都會被設定成500(在程式中通過getresuid函數來擷取進程的三個UID的值),運行結果如:
接下來我們再來討論第二種情況,如果函數的參數uid等於進程的實際使用者ID或者儲存的設定使用者ID,那麼就將有效使用者ID設定為uid,其他的兩個(實際使用者ID和儲存的設定使用者ID)保持不變。比如下面這個程式:
1 #include<sys/types.h> 2 #include<unistd.h> 3 #include<stdio.h> 4 void err_exit(char *fmt,...); 5 int main(int argc,char *argv[]) 6 { 7 uid_t ruid,euid,suid; 8 9 printf("Before:\n");10 if(-1 == getresuid(&ruid,&euid,&suid))11 err_exit("[getresuid]: ");12 printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);13 setuid(500);14 printf("After:\n");15 if(-1 == getresuid(&ruid,&euid,&suid))16 err_exit("[getresuid]: ");17 printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);18 19 return 0;20 }
關於這段代碼產生的可執行檔,我讓它的擁有者變成了xiaojie(UID=501),並且給它加上了SUID許可權,如所示:
然後我用michael使用者(UID=500)來運行這個程式,那麼這個程式的實際使用者ID=500,而有效使用者ID和儲存的設定使用者ID都為501。接著,在這個程式中調用setuid函數,傳入的參數為500,大家可以猜想到,最終應該只有有效使用者ID受到影響,而儲存的設定使用者ID保持不變。運行結果如所示:
這就符合了我們的預期,僅僅只有有效使用者ID受到了影響,而實際使用者ID和儲存的設定使用者ID並沒有改變。(這裡我僅僅是驗證了儲存的設定使用者ID不受影響,大家也可以自己寫一個程式,來驗證實際使用者ID並不受影響。)
最後一點,如果setuid函數的參數的uid的值即不等於實際使用者ID,也不等於儲存的設定使用者ID,而且這個進程的有效使用者ID也不是0(非特權進程)。那麼這個程式就會出錯,並將errno設定為EPERM。比如下面這個程式:
1 #include<sys/types.h> 2 #include<unistd.h> 3 #include<stdio.h> 4 #include<string.h> 5 #include<errno.h> 6 int main(int argc,char *argv[]) 7 { 8 uid_t ruid,euid,suid; 9 10 getresuid(&ruid,&euid,&suid);11 printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);12 13 if(-1 == setuid(500))14 {15 printf("%s\n",strerror(errno));16 if(errno == EPERM)17 {18 printf("Error number is EPERM!\n");19 }20 }21 else22 {23 getresuid(&ruid,&euid,&suid);24 printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);25 }26 27 return 0;28 }
我以xiaojie使用者(UID=501)來運行這個程式,那麼這個程式的三個使用者ID都為501,然後在這個程式中,調用setuid(500)。於是程式就會報錯,並將errno設定為EPERM,如所示:
OK,這就是APUE上關於setuid函數的解釋,講了這麼多,綜合成一句話就是,root是神,想怎麼幹就怎麼幹,沒人管得了。而普通使用者呢,只能管自己分內的事(把進程的有效ID改成自己的ID),不能胡亂來。
OK,that‘s all。希望這篇文章對大家能夠有協助。