system()函數的聲明和說明如下:
注意它的描述那裡,system()執行一個由command參數定義的命令,通過調用/bin/sh -c命令來實現這個功能。也就是說它的邏輯是這樣的!
進程調用system函數,system函數調用fork創建一個子進程,然後再調用exec函數來把這個子進程的正文段替換成/bin/sh命令的正文段。然後再由sh來執行exec將程序的正文段替換成command參數所代表的命令的正文段,例如,我的一個程序a.out來調用system函數來執行sleep 20命令,它的進程示意圖如下所示:
可以參考下面這個例子,如下圖所示:
這裡我執行了一個system文件,產生了兩個進程,3994和3995(右邊那個終端所示,第一列是PPID,第二列是PID),這兩個進程是父子關系,值得注意的是這兩個進程進程ID是連著的,也就是說在這兩個進程執行的時候沒有新的進程產生。
接下來開始扯正題,在《APUE》(UNIX環境高級編程,下文不再贅述)的8.13節中,作者強調SUID和SGID程序中不應該調用system函數。為什麼呢,我個人的理解是這樣的。
以SUID權限為例,SUID這種權限設立的目的是什麼,就是提供一種可控的超級權限。比如passwd命令,運行passwd這個程序後,進程的有效用戶ID是root,理論上可以為所欲為(即對shadow這個文件想怎麼改就怎麼改),但是passwd程序的代碼已經被寫死了,用戶所做的操作都要經過passwd這個程序的檢驗,符合標准了才能夠進行,否則程序就會提示出錯!(也就是說不能像vim那樣隨意對shadow文件進行更改,只能在某種規范下進行更改)。
同時,這種權限應該是有限制的,不能夠進行任意傳播。比如,像man這種能夠執行shell命令的程序,它執行shell命令就是通過fork-exec機制來執行了,在某些distribution中有一個man用戶,man程序屬於這個用戶,並且設置了SUID位。也就是說我任意一個普通用戶運行man程序後的有效用戶都是man,如果此時這個普通用戶在man程序中執行shell命令的話,這個shell命令進程的有效用戶應不應該是man呢,設置用戶ID應不應該繼續保留呢,當然不行啦,這樣的話一個普通用戶不就能夠通過這種方式來具有了man用戶的權限了!(如果保留了設置用戶ID,那麼在子進程中可以調用setuid函數來使進程的有效用戶ID變成設置用戶ID,同樣能達到上面所說的目的)
OK,上面說的這麼一大串,就是為啥SUID或者SGID程序不應該調用system函數來執行一個shell命令,因為這樣會傳播進程的設置用戶ID和有效用戶ID,將它傳遞給子進程,這樣就會產生bug了。理論上是這樣的,但是實際中我發現貌似不行,我的CentOS6.6上就不能模擬出這個bug來,比如我有這麼一段程序:
getresuid程序的代碼如下:
/* 這個程序的作用是獲取進程的三個用戶ID,它的可執行文件被做了一個軟鏈接到/home/michael/bin下面
* */
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#define BUFSIZE 512
void err_exit(char *fmt,...);
int main(int argc,char *argv[])
{
uid_t ruid,euid,suid;
if(-1 == getresuid(&ruid,&euid,&suid))
err_exit("[getresuid]: ");
printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);
return 0;
}
system程序的代碼如下:
#include<stdlib.h>
#include<stdio.h>
int main(int argc,char *argv[])
{
uid_t ruid,euid,suid;
if(-1 == getresuid(&ruid,&euid,&suid))
err_exit("[getresuid]: ");
printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);
system("getresuid");
return 0;
}
這個system程序的功能就是首先輸出進程的real uid,effective uid,和set-user id,然後再來通過system函數調用getresuid程序再來把這三個uid輸出一遍,我把system這個可執行文件變成root所有,並加上suid權限,執行的結果如下圖:
第一個uid的輸出結果是符合預期的,即由於SUID權限位的設置,有效用戶ID是0。但是後面第二個uid的輸出卻和想象的有點不同,理論上,system函數應該是能夠傳遞set-user ID和有效用戶ID給子進程的,但是這裡三個UID全部都變成了500,沒一個是root,這個可能是因為system函數在exec之前將所有三個UID都變成了實際用戶ID,或者是sh命令在exec之前將所有三個UID都變成了實際用戶ID。
照這麼看來,貌似在SUID程序中調用system函數也未嘗不可,但是為了保險起見,還是自己通過fork和exec動手來實現一個system吧,然後在exec之前把三個UID都設置成實際用戶ID。