Linux的使用者在登入(login)之後,就帶有一個使用者身份(user ID, UID)和一個組身份(group ID, GID)。在Linux檔案管理背景知識中,我們又看到,每個檔案又有九位的許可權說明,用來指明該檔案允許哪些使用者執行哪些操作(讀、寫或者執行)。
一般來說,Linux的使用者資訊儲存在/etc/passwd中,組資訊儲存在/etc/group中,檔案的每一行代表一個使用者/組。早期的Linux將密碼以名碼的形式儲存在/etc/passwd中,而現在則多以暗碼(也就是加密之後的形式)的形式儲存在/etc/shadow中。將密碼儲存在/etc/shadow中提高了密碼的安全性,因為/etc/passwd允許所有人檢視,而/etc/shadow只允許root使用者檢視。
1. 程式許可權
但是,在Linux中,使用者的指令是在程式的 範圍內進行的。當我們向對某個檔案進行操作的時候,我們需要在程式中執行一個程式,在程式中對檔案開啟,並進行讀、寫或者執行的操作。因此,我們需要將用 戶的許可權傳遞給程式,以便程式真正去執行操作。例如我們有一個檔案a.txt, 檔案中為一個字串:
Hello world!
我以使用者Vamei的身份登入,並在shell中執行如下命令:
$cat a.txt
整個執行過程以及檔案讀取如下:
我們可以看到,整個過程中我們會有兩個程式,一個是shell本身(2256),一個是shell複製自身,再執行/bin/cat (9913)。圖中的fork, exec, PID可參看Linux程式基礎。第二個程式總共對檔案系統進行了兩次操作,一次是執行(x)檔案/bin/cat,另外一次是讀取(r)檔案a.txt。使用$ls -l 檢視這兩個檔案的許可權:
$ls -l /bin/cat
-rwxr-xr-x 1 root root 46764 Apr 1 2012 /bin/cat
$ls -l a.txt
-rw-rw-r-- 1 Vamei Vamei 14 Oct 7 09:14 a.txt
從上面可以看到(參考Linux檔案管理背景知識),/bin/cat讓所有使用者都享有執行的權利,而Vamei作為a.txt的擁有者,對a.txt享有讀取的權利。
讓我們進入更多的細節 (The devil is in the details)。在進行這兩次操作的時候,儘管使用者Vamei擁有相應的許可權,但我們發現,真正做工作的是程式9913。我們要讓這個程式得到相應的許可權。實際上,每個程式會維護有如下6個ID:
真實身份: real UID, real GID
有效身份: effective UID, effective GID
儲存身份:saved UID, saved GID
其中,真實身份是我們登入使用的身份,有效身份是當該程式真正去操作檔案時所檢查的身份,儲存身份較為特殊,我們等一下再深入。當程式fork的時候,真實身份和有效身份都會複製給子程式。大部分情況下,真實身份和有效身份都相同。當Linux完成開機啟動之 後,init程式會執行一個login的子程式。我們將使用者名稱和密碼傳遞給login子程式。login在查詢了/etc/passwd和/etc /shadow,並確定了其合法性之後,執行(利用exec)一個shell程式,shell程式真實身份被設定成為該使用者的身份。由於此後fork此 shell程式的子程式都會繼承真實身份,所以該真實身份會持續下去,直到我們登出並以其他身份再次登入(當我們使用su成為root的時候,實際上就是 以root身份再次登入,此後真實身份成為root)。
2. 最小許可權原則
每個程式為什麼不簡單地只維護真實身份,卻選擇費盡麻煩地去維護有效身份和儲存身份呢?這牽涉到Linux的“最小特權”(least priviledge)的 原則。Linux通常希望程式只擁有足夠完成其工作的特權,而不希望賦予更多的特權給它。從設計上來說,最簡單的是賦予每個程式以super user的特權,這樣程式就可以想做什麼做什麼。然而,這對於系統來說是一個巨大的安全漏洞,特別是在多使用者環境下,如果每個使用者都享有無限制的特權,就 很容易破壞其他使用者的檔案或者系統本身。“最小特權”就是收縮排程所享有的特權,以防程式濫用特權。
然而,程式的不同階段可能需要不同的特權。比如一個程式最開始的有效身份是真實身份,但執行到中間的時候,需要以其他的使用者身份讀入某些配置檔案,然後再進行其他的操作。為了防止其他的使用者身份被濫用,我們需要在操作之前,讓程式的有效身份變更回來成為真實身份。這樣,程式需要在兩個身份之間變化。
儲存身份就是真實身份之外的另一個身份。當我們將一個程式檔案執行成為程式的時候,該程式檔案的擁有者(owner)和擁有組(owner group)可以被,儲存成為程式的儲存身份。在隨後程式的執行過程中,程式就將可以選擇將真實身份或者儲存身份複製到有效身份,以擁有真實身份或者儲存身份的許可權。並不是所有的程式檔案在執行的過程都設定儲存身份的。需要這麼做的程式檔案會在其九位(bit)許可權的執行位的x改為s。這個時候,這一位(bit)叫做set UID bit或者set GID bit。
$ls -l /usr/bin/uuidd
-rwsr-sr-x 1 libuuid libuuid 17976 Mar 30 2012 /usr/sbin/uuidd
當我以root(UID), root(GID)的真實身份執行這個程式的時候,由於擁有者(owner)有s位的設定,所以saved UID被設定成為libuuid,saved GID被設定成為libuuid。這樣,uuidd的程式就可以在兩個身份之間切換。
我們通常使用chmod來修改set-UID bit和set-GID bit:
$chmod 4700 file
我 們看到,這裡的chmod後面不再只是三位的數字。最前面一位用於處理set-UID bit/set-GID bit,它可以被設定成為4/2/1以及或者上面數字的和。4表示為set UID bit, 2表示為set GID bit,1表示為sticky bit (暫時不介紹)。必須要先有x位的基礎上,才能設定s位。
作為一個Linux使用者來說,我們並不需要特別關心上面的機制。但是,當我們去編寫一個Linux應用程式的時候,就要注意在程式中實現以上切換(有必要的前提下),以便讓我們的程式符合"最小許可權"的原則,不給系統留下可能的安全隱患。