1. 程式人生 > >第9章 程序憑證

第9章 程序憑證

每個程序都有一組與之相關的數值型使用者識別符號(UIDs)和組識別符號(GIDs)。有時,把這些識別符號稱之為 程序憑證(process credentials) 。這些識別符號有:

  • 實際 【真實】 (real)使用者ID和實際組ID;
  • 有效(effective)使用者ID和有效組ID;
  • 儲存的set-user-ID(saved set-user-ID)和儲存的set-group-ID;
  • 檔案系統使用者ID和檔案系統組ID(Linux特有);
  • 輔助組 【補充組】 (supplementary group)IDs。

本章中,將 【we,我們】 詳細探究上述程序識別符號的用途 【the purpose of,作用】

,並且介紹用於獲取或修改上述識別符號的系統呼叫和庫函式。還將討論 【we also discuss, 我們還會討論】 特權程序和非特權程序的概念,並闡述 【and,以及】 set-user-ID和set-group-ID的機制,採用該機制所建立的程式可以以特定使用者或組的許可權執行。【which allow the creation of programs that run with the privileges of a specified user or group,用於程式建立,這些程式以指定使用者或組的特權進行執行】

9.1 Real User ID and Real Group ID

實際使用者ID(Real User ID)實際組ID(Real Group ID) 用於標識使用者屬於哪個使用者和組。作為登入過程的一部分 【As part of the login process,這裡process的意思不是“程序”而是“過程”】 ,login shell 【作為login程序的部分,】 會從/etc/passwd檔案(8.1節)讀取相應使用者密碼記錄的第三個(UID)和第四個欄位(GID),從而得到實際使用者ID和實際組ID。當新的程序被建立時(例如使用shell建立程式時),它將 【會】 從父程序中繼承這些識別符號。

9.2 Effective User ID and Effective Group ID

在大部分UNIX實現中(Linux系統略有不同,9.5節介紹),當程序嘗試執行各種操作(即 【i.e., 也就是,】 系統呼叫)時,將結合 有效使用者ID(Effective User ID)有效組ID(Effective Group ID) 和輔助組ID(supplementary group IDs)一起確認程序是否擁有許可權。舉例來說,當程序訪問資源(例如檔案和System V程序間通訊物件)時,上述IDs會根據這些資源所屬的使用者ID和組ID,決定是否授予程序訪問許可權。如20.5節所述~~【 As we’ll see
in Section 20.5,在20.5節中】~~ ,核心還會使用有效使用者ID決定程序是否可以向其他程序傳送訊號。
具有有效使用者ID為0(root的使用者ID)的程序具有超級使用者的所有許可權。此類程序被稱之為 特權程序(privileged process)。某些系統呼叫只有特權程序才有許可權執行。

在39章中,將闡述Linux capability的實現,capability機制將授予超級使用者的許可權劃分成很多不同的單元,這些單元可以獨立地啟用 【enabled,開啟】 和禁用 【disabled,關閉】

通常,有效使用者ID和有效組ID與對應的實際使用者ID和實際組ID的值相同。但是有兩種方式可以將effective IDs設定成不同的值,一種方式是使用9.7節的系統呼叫,第二種方式是執行 【通過】 set-user-ID 和 set-group-ID 程式 【的執行】

9.3 Set-User-ID and Set-Group-ID programs

set-user-ID程式將程序的有效使用者ID設定成與可執行檔案的使用者ID(檔案屬主)相同的值,從而使程序獲取它平時所沒有的許可權。set-group-ID程式為程序的有效組ID執行類似的任務。(專業術語 set-user-ID程式set-group-ID程式 有時簡寫成 set-UID程式set-GID程式 。譯者注:甚至可以簡寫為 SUID程式SGID程式
像其他檔案一樣,採用使用者ID和組ID來定義可執行程式的所有權。此外,可執行檔案具有兩個特別的許可權位(permission bits):set-user-ID位和set-group-ID位。(實際上 【In fact,事實上】 ,每個檔案都有這兩個許可權位,但此處只 關注 可執行檔案的許可權位 【but it is their use with executable files that interests us here,但是這裡我們只對可執行檔案的 感興趣 。)使用 chmod 命令對許可權位進行設定。非特權使用者只能 對其擁有 的檔案進行設定 【An unprivileged user can set these bits for files that they own,非特權使用者只能設定 它們自己所擁有檔案的許可權】 。特權使用者可以設定任何檔案的許可權位。下面是一個例子:
在這裡插入圖片描述
正如上例所示,程式可以同時對這兩個位進行設定,儘管這並不常見。當使用 ls -l 檢視程式(檔案)的許可權時,如果程式設定了set-user-ID或set-group-ID許可權位,那麼通常用於表示 可執行(execute) 許可權的位(x)會被s替代:
在這裡插入圖片描述
set-user-ID程式 執行時(即使用exec()將程式載入到程序的記憶體中),核心將程序的有效使用者ID設定成可執行檔案的使用者ID。執行set-group-ID程式時,程序的有效組ID具有類似的效果。採用這種方法(換句話說,使用者執行程式)改變程序的有效使用者ID和有效組ID,可以使程序獲得通常所沒有的許可權。例如,如果一個可執行檔案的屬主是root(超級使用者),並且開啟了set-user-ID許可權,那麼當程式執行時程序將獲得超級使用者許可權。
set-user-ID程式和set-group-ID程式還可以將程序的有效IDs(有效使用者ID和有效組ID)改成除了root之外的其他使用者IDs。例如,為了訪問某個受保護的檔案(或者其他系統資源),專門為此建立一個擁有訪問這些檔案許可權的使用者(或組)以及set-user-ID(set-group-ID)程式,這樣程序的有效使用者(組)ID就可以改為這些IDs。這就使得程式可以訪問這些檔案,但是不具有超級使用者的所有許可權。
有時,使用術語 set-user-ID-root程式 來區分set-user-ID程式,前者的屬主是root,後者的屬主是其他使用者,只給予程序相應使用者的許可權。

從現在開始,使用的術語“特權(privileged)”有兩層不同意思。其一是之前定義的:有效使用者ID為0的程序,它具有root使用者的所有特權。其二是當我們討論到set-user-ID程式的所屬使用者(非root)時,指程序獲取了該set-user-ID程式的使用者ID的特權。術語“特權”是指哪種意思,我們可以通過上下文來判別。

Linux中set-user-ID程式的常用例子有:用於更改使用者ID的 passwd命令;用於掛載(mount)和解除安裝(unmount)檔案系統的 mountunmount命令;用於在不同使用者ID下執行shell的 su命令。set-group-ID程式的一個例子是:wall,用於將訊息寫入到所屬組是tty組的所有終端中(通常,所有終端都屬於這個組)。
在8.5節中,我們注意到Listing 8-2中的程式需要使用root賬號登入後才能訪問/etc/shadow檔案。我們可以通過將該程式設定為set-user-ID-root程式,這樣任意的使用者都能執行。如下:
在這裡插入圖片描述
在這裡插入圖片描述

set-user-ID/set-group-ID技術是一種有用和強大的工具。但是如果應用沒有進行良好的設計,會導致 安全隱患 【 security breaches,安全問題】 。38章中我們列出一套在編寫set-user-ID和set-groupID程式時應當遵循的 良好程式設計習慣 【 good practices,良好做法】

摘自網上的一個對set-user-ID的解釋
set-user-ID 會建立s與t許可權,是為了讓一般使用者在執行某些程式的時候,能夠暫時具有該程式擁有者的許可權。舉例來說,我們知道,賬號與密碼的存放檔案其實是 /etc/passwd與 /etc/shadow。而 /etc/shadow檔案的許可權是“----------”。它的擁有者是root。在這個許可權中,僅有root可以“強制”儲存,其他人是連看都不行的。但是,偏偏筆者使用dmtsai這個一般身份使用者去更新自己的密碼時,使用的就是 /usr/bin/passwd程式,卻可以更新自己的密碼。也就是說,dmtsai這個一般身份使用者可以存取 /etc/shadow密碼檔案。這怎麼可能?明明 /etc/shadow就是沒有dmtsai可存取的許可權。這就是因為有s許可權的幫助。當s許可權在user的x時,也就是類似 -r-s–x--x,稱為set-user-ID,這個user-ID表示使用者的ID,而user表示這個程式(/usr/bin/passwd)的擁有者(root)。那麼,我們就可以知道,當dmtsai使用者執行 /usr/bin/passwd時,它就會“暫時”得到檔案擁有者root的許可權。
set-user-ID僅可用在“set-user-ID”,set-user-ID因為是程式在執行過程中擁有檔案擁有者的許可權,因此,它僅可用於二進位制檔案,不能用在批處理檔案(shell指令碼)上。這是因為shell指令碼只是將很多二進位制執行檔案調進來執行而已。所以set-user-ID的許可權部分,還是要看shell指令碼呼叫進來的程式設定,而不是shell指令碼本身。當然,set-user-ID對目錄是無效的。這點要特別注意。

9.4 Saved Set-User-ID and Saved Set-Group-ID

saved set-user-IDsaved set-group-ID 是為了set-user-ID程式和set-group-ID程式而設計的。當程式執行時,會有以下步驟:

  1. 開啟了可執行檔案的 【如果可執行檔案開啟了】 set-user-ID(set-group-ID)許可權位。那麼程序的有效使用者(組)ID會被設定成可執行檔案的屬主。若未 設定set-user-ID(set-group-ID)許可權位 【如果set-user-ID(set-group-ID)位沒有設定】 ,那麼程序的有效使用者(組)ID保持不變。
  2. saved set-userID 和 saved set-group-ID的值從對應的有效使用者ID和有效組ID中複製過來。不管set-user-ID或者set-group-ID許可權位是否開啟,這個步驟(複製行為)都會執行。

舉例說明上述步驟的影響:假設程序的使用者ID、有效使用者ID和saved set-user-ID都是1000,執行了屬主為root(使用者ID為0)的set-user-ID程式。在執行後,程序的IDs將變為:
在這裡插入圖片描述

有不少 【various,各種】 系統呼叫允許set-user-ID程式將有效使用者ID值在實際使用者ID和saved set-user-ID之間隨意切換。類似的系統呼叫允許set-group-ID程式修改它的有效組ID。以這種方式,程式可以臨時將與執行檔案的使用者(組)ID相關的許可權 放棄(drop)重新獲取(regain) 。(換句話說,程式可以有兩種許可權狀態:自己的許可權和set-user-ID程式屬主的許可權)正如38.2節所述,在set-user-ID程式和set-group-ID程式中,當程式實際不需要使用特權ID(即saved set-user-ID)執行操作,就切換到非特權ID(即實際使用者ID),這是一個安全的程式設計習慣。

saved set-user-ID和save set-group-ID有時也稱為 save user IDsaved group ID

9.5 File-System User ID and File-System Group ID

在Linux中,執行檔案操作(例如開啟檔案、改變檔案所屬權和修改檔案許可權)時,使用 檔案系統使用者ID(File-System User ID)檔案系統組ID(File-System Group ID)(與輔助組ID相結合) 決定操作許可權,而不是有效使用者ID和有效組ID。(像其他UNIX實現一樣,有效使用者ID和有效組ID仍在使用,用途如上一節所述。)
通常,檔案系統使用者ID和檔案系統組ID與對應的有效使用者ID和有效ID具有相同的值(一般與對應的實際使用者ID和實際組ID的值也相同)。此外,每當使用系統呼叫或者set-userID(set-group-ID)程式對有效使用者ID(有效組ID)進行修改時,對應的檔案系統ID也會改為相同的值。因為檔案系統IDs以這種方式跟隨有效IDs進行變化。這意味著,檢查特權和許可權時,Linux的行為與其他UNIX實現的行為類似。只有當使用Linux 特有 的兩個系統呼叫:setfsuid()setfsgid() 時,檔案系統IDs才會與有效IDs不同。
為什麼Linux要提供檔案系統IDs,在什麼情況下我們才希望有效IDs與檔案系統IDs不同?這原因主要跟歷史有關。檔案系統IDs首次出現在Linux1.2中。在這個核心版本中,如果某個進行傳送訊號給另一個程序,那麼需要傳送程序的 有效使用者ID 與目標程序的實際使用者ID或有效使用者ID匹配。這就影響到了某些程式,如Linux NFS(網路檔案系統)服務程式,這些程式需要能夠訪問檔案,就像它擁有相應客戶端程序的有效IDs。然而,如果NFS服務程式改變了它的有效使用者ID,那麼容易受到非特權使用者程序的發來的訊號攻擊( it would be vulnerable to signals from unprivileged user processes)(譯者注:客戶端程序發過來的訊號找不到服務端的服務程序,導致客戶端不斷往服務端傳送訊號?這塊內容有點看不明白)。為了防止這種可能性,【在設計中增加了】 檔案系統使用者ID和檔案系統組ID 應運而生(were devised)。NFS服務的有效IDs保持不變,只通過改變檔案系統IDs從而偽裝成另一個使用者,這樣即達到了訪問檔案的目的,又避免遭受訊號攻擊。
從核心2.0開始,Linux 在訊號傳送許可權方面 開始採用SUSv3強制規定 【關於傳送訊號許可權方面】 的規則。這些規則不涉及目標程序的有效使用者ID(參考20.5節)。這樣,就不再需要檔案系統ID這個特性了,但是為了相容已存在的軟體,這個特色還是被保留了下來。
因為檔案系統ID 實屬異類 【something of an oddity,有點奇怪】 ,並且通常與對應的有效IDs的值相同。在本書的剩下章節中,我們以程序的有效IDs來描述各種許可權檢查。雖然在Linux程序進行許可權檢查時,實際可能用到了檔案系統ID。但實際上,它們的存在 並不會帶來顯著差別【their presence seldom makes an effective differenc】。

9.6 Supplementary Group IDs

輔助組ID(Supplementary Group ID) 是程序所屬的額外組。程序從父程序中繼承這些IDs。login shell從系統組檔案(/etc/group)中獲取輔助組IDs。正如上所述,這些IDs用於結合有效IDs和檔案系統IDs,從而決定訪問檔案、System V IPC物件和其他系統資源的許可權。

9.7 Retrieving and Modifying Process Credentials

Linux提供了一系列系統呼叫和庫函式,用於獲取和改變上面幾節中的各種使用者ID和組ID。其中只有部分APIs才在SUSv3中進行了定義。剩下的APIs,有些在其他UNIX實現中被廣泛使用,有些是Linux特有的。在闡述各種介面時,我們需要注意可移植性的問題。在這節的末尾,Table 9-1對用於改變程序憑證的所有介面操作做了概括。
除了使用下面描述的各種系統呼叫,還可以使用Linux特有的 /proc/PID/status 檔案中的 UIDGIDGroups 來檢視程序憑證。UID(GID)中的識別符號分別表示:實際使用者ID(實際組ID)、有效使用者ID(有效組ID)、saved set-user-ID (saved set-group-ID)和檔案系統使用者ID(檔案系統組ID)。
在這裡插入圖片描述

下面章節中,我們使用 有效使用者ID為0的程序稱為 特權程序 這一傳統定義。然而,在Linux中將超級使用者特權劃分成不同的 能力(capability)(39章)。有兩個 能力會在修改程序使用者ID和組ID的這些系統呼叫用到:

  • CAP_SETUID 能力允許程序對它們的使用者IDs做任意修改。
  • CAP_SETGID 能力允許程序對它們的組IDs做任意修改。

9.7.1 Retrieving and Modifying Real,Effective,and Saved Set IDs

接下來,將介紹用於獲取和修改實際IDs、有效IDs和saved set IDs的這些系統呼叫。有若干系統呼叫是用於執行這些任務的,在某些情況下,它們的功能可能會重疊,反映出不少系統呼叫來源於不同的UNIX實現這一實際情況。

Retrieving real and effective IDs

getuid()getgid() 系統呼叫分別返回呼叫程序的實際使用者ID和實際組ID。geteuid()getegid() 系統呼叫為有效IDs執行相應的任務。這些系統呼叫總是能呼叫成功。

#include <unistd.h>
// 返回呼叫程序的實際使用者ID
uid_t getuid(void);
// 返回呼叫程序的有效使用者ID
uid_t geteuid(void);
// 返回呼叫程序的實際組ID
gid_t getgid(void);
// 返回呼叫程序的有效組ID
gid_t getegid(void);

Modifying effective IDs

setuid() 系統呼叫將程序的有效使用者ID(還有可能將實際使用者ID和saved set-user-ID)設定成給定的uid引數的值。setgid() 系統呼叫為對應的組IDs執行類似的任務。

#include <unistd.h>
//執行成功時返回0,失敗時返回-1
int setuid(uid_t uid);
int setgid(gid_t gid);

使用setuid()和setgid()可以對程序憑證做哪些改變呢?這主要取決於是否是特權程序(有效使用者ID是0的程序)。setuid()有下列規則:

  1. 當非特權程序呼叫setuid()時,只有程序的有效使用者ID會改變。此外,它只能被設定成實際使用者ID和saved set-user-ID的值(如果違反這個約束,會產生EPERM錯誤)。這意味著,對於非特權使用者,這個系統呼叫只有在執行set-user-ID程式時才有用。因為對於正常程式的執行,程序的實際使用者ID、有效使用者ID和saved set-user-ID都是相同的值。在一些派生自BSD的實現(系統)中,通過非特權程序呼叫setuid()或getgid()具有與其他UNIX實現(系統)不同的語義:系統呼叫會將實際IDs、有效IDs和saved set IDs修改成當前的實際IDs或有效IDs的值。
  2. 當特權程序執行setuid(),傳入一個非0引數,那麼 實際使用者ID有效使用者IDsaved set-user-ID 都會被設定成uid引數所指定的值。這是一波單向(one-way)操作,一旦特權程序以這種方式改變了它的識別符號(IDs)後,它就失去了所有特權(由程序的有效使用者ID決定),因此隨後就不能使用setuid()將這些識別符號重新設定回0了。如果這不是你想要的,那麼可以使用隨後介紹的 seteuid()setreuid() 來替代setuid()。

使用 setgid() 對組ID進行設定的規則也是類似的,只是用 setgid() 代替 setuid(),用 代替 使用者。setgid()的規則1與上面所述相同。在規則2中,因為改變組不會導致程序喪失特權(由程序的有效使用者ID決定),特權程式可以使用setgid()自由地將組ID改成想要的值。
將set-user-ID-root程式(當前有效使用者ID是0)以不可逆的方式放棄所有特權(通過將有效使用者ID和saved set-user-ID設為與實際使用者ID相同的值)的首先方式是使用下列呼叫:

if (setuid(getuid()) == -1)
	errExit("setuid");

出於9.4節所述的安全原因,屬主不是root的set-user-ID程式可以使用setuid()將有效使用者ID在實際使用者ID和saved set-user-ID的值之間切換。但是如果僅僅考慮這個原因,那麼seteuid()會使更好的選擇,因為它具有相同的效果,而不需要考慮set-user-ID程式的屬主是否是root。
程序可以通過使用 seteuid() 將它的有效使用者ID改成euid引數所指定的值。setegid() 將有效組ID改成egid引數所指定的值。

#include <unistd.h>

//執行成功時返回0,錯誤時返回-1
int seteuid(uid_t euid);
int setegid(gid_t egid);

程序在使用seteuid()和setegid()對程序的有效IDs進行改變時,會有以下規則:

  1. 非特權程序只能將有效ID設定成相應的實際ID和save set ID。(換句話說,對於非特權程序,seteuid()和seteuid()分別與setuid()和setgid()的效果相同嗎,之前介紹的BSD移植性問題。)
  2. 特權程序可以將有效ID設定成任意的值。如果特權程序使用seteuid()將有效使用者ID改成非0的值,那麼它不再具有特權(但是有時可以通過第一條規則重新獲取特權)

set-user-ID和set-group-ID程式臨時放棄特權,隨後又重新獲取特權的首選方法是使用seteuid()。如下例:


/*
假設初始IDs: real=1000,effective=0,saved=0 
儲存剛開始的有效使用者ID,它的值與saved set-user-ID相同語句。執行後變數euid=0 
*/
euid=geteuid(); 

/*
有效使用者ID被設為1000,去除特權
執行後的IDs:real=1000,effective=1000,saved=0 
*/
if (seteuid(getuid()) == -1) 
	errExit("seteuid");

/*
根據上述規則1:非特權程序只能將有效ID設定成相應的實際ID和save set ID
因為save set ID是0,所以允許將有效使用者ID重新設定為0
執行後的IDs:real=1000,effective=0,saved=0
*/
if (seteuid(euid) == -1) 
	errExit("seteuid");

seteuid()和setegid()來源於BSD,現在已成為SUSv3的規範,出現在大部分的UNIX實現中。

在GNU C庫的舊版本中(glibc2.0及更早版本),seteuid(euid)被實現為setreuid(-1,euid)。在現代的glibc版本中,seteuid(euid)被實現為setresuid(-1, euid, -1)。兩種實現都允許我們將euid指定為與當前有效使用者ID相同的值(即保持不變)。但是SUSv3中沒有規定seteuid()的這種行為,其他UNIX實現也不支援。
通常情況下,有效使用者ID要麼與實際使用者ID相同,要麼與saved set-user-ID相同。(Linux中要使有效使用者ID既不同於實際使用者ID,也不同於save set-user-ID的唯一方法是使用非標準的setresuid()系統呼叫。)

Modifying real and effective IDs

setreuid() 系統呼叫允許我們獨立地改變實際使用者ID和有效使用者ID。setregid() 系統呼叫為實際組ID和有效組ID執行類似的任務。

#include <unistd.h>
// 成功時返回0,發生錯誤時返回-1
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

每個系統呼叫的第一個引數是新的實際ID。第二個引數是新的有效ID。如果我們只想改變其中的一個識別符號,那麼只需要將其他引數指定為-1。
setreuid()和setregid()起初來源於BSD,現在已是SUSv3的規範,並且已被大部分UNIX實現支援。
與這節中的其他系統呼叫一樣,setreuid()和setregid()也有以下規則。我們只從setreuid()的角度闡述這些規則,setregid()也是類似的。

  1. 非特權程序只能將實際使用者ID設定成當前的實際使用者ID(即保持不變)或有效使用者ID的值。有效使用者ID只能設定成當前實際使用者ID、有效使用者ID(即保持不變)或者saved set-user-ID。
  2. 特權程序可以對這些IDs做任何改變。
  3. 只要下面條件成立,不管是特權程序還是非特權程序,saved set-user-ID都會被設定成新的有效使用者ID:
    a) ruid不是-1(即實際使用者ID被設定,即使被設定成了與現在實際使用者ID相同的值)。
    b) 有效使用者ID被設定成不同與系統呼叫之前的實際使用者ID
    反過來說,如果程序使用setreuid()只將有效使用者ID改成與當前實際使用者ID相同的值,那麼saved set-user-ID保持不變。 隨後可以通過呼叫setreuid()(或seteuid())將有效使用者ID恢復成saved set-user-ID的值。

第3條規則提供一種set-user-ID程式永久放棄特權的方法,使用如下系統呼叫:

setreuid(getuid(), getuid());

set-user-ID-root程序想要對使用者和組憑證都修改成任意的值,應該首先呼叫setregid(),然後呼叫setreuid()。如果呼叫的先後順序相反,那麼呼叫setregid()時將失敗,因為呼叫了setreuid()之後,程式不再是具有特權的了。

Retrieving real,effective,and saved set IDs

大部分UNIX實現中,程序不能直接獲取(或更改)saved set-user-ID和set-group-ID。但是Linux提供了兩種(非標準的)系統呼叫,允許我們可以這麼做:getresuid()getresgid()

#define _GNU_SOURCE
#include <unistd.h>
// 成功時返回0,失敗時返回-1
int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
int getresig(gid_t *rgid, gid_t *geid, gid_t *sgid);

getresuid()系統呼叫返回呼叫程序中三個引數指向的實際使用者ID、有效使用者ID和saved set-user-ID的值。getresgid()類似。

Modifying real,effective,and saved set IDs

setresuid() 系統呼叫允許呼叫程序獨立地改變使用者IDs的這三個值。新的使用者IDs的值由系統呼叫中的三個引數指定。**setresgid()**為組ID執行類似的任務。

#define _GNU_SOURCE
#include <unistd.h>

//成功時返回0,失敗時返回-1
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);

如果我們不想改變所有的識別符號,那麼將該引數設為-1,將保持該識別符號不變。例如下面的呼叫等價於seteuid(x):

setresuid(-1, x, -1);

setresuid()(setresgid()類似)的規則如下:

  1. 非特權程序可以將實際使用者ID、有效使用者ID和saved set-user-ID的任意ID設定成當前的實際使用者ID、有效使用者ID或者saved set-user-ID中的任意值。
  2. 特權進行可以對實際使用者ID、有效使用者ID和saved set-user-ID做任何改變。
  3. 不管呼叫是否對其他IDs做了改動,檔案系統使用者ID總是被設定成與(可能是新的)有效使用者ID相同的值。

呼叫setresuid()和setresgid()是原子性(all-or-nothing effect)的。要麼請求的所有識別符號都被修改成功,要麼都失敗。(這也適用於本章中其他修改多個ID的系統呼叫)
儘管setresuid()和setresgid()為修改程序憑證提供了更加簡明直白的API。但是我們不能將它們用於可移植的應用中。因為它們不是SUSv3的規範,其他的一些UNIX系統並沒有對它們進行支援。

9.7.2 Retrieving and Modifying File-System IDs

之前描述的所有系統呼叫在改變程序的有效使用者ID或有效組ID時總會修改對應的檔案系統ID。想要單獨地修改檔案系統IDs,必須採用兩個Linux特有的系統呼叫:setfsuid()setfsgid()

#include <sys/fsuid.h>
// Always returns the previous file-system user ID
int setfsuid(uid_t fsuid);
// Always returns the previous file-system group ID
int setfsgid(gid_t fsgid);

setfsuid()系統呼叫將程序的檔案系統使用者ID修改成fsuid引數所指定的值。setfsgid()系統呼叫將程序的檔案系統組ID修改成fsgid引數所指定的值。
setfsuid()的規則如下(setfsgid類似):

  1. 非特權程序可以將檔案系統使用者ID設定成當前的實際使用者ID、有效使用者ID、檔案系統使用者ID (即保持不變)或者saved set-user-ID的值。
  2. 特權程序可以將檔案系統使用者ID設定成任何值。

setfsuid()和setfsgid()系統呼叫的使用在Linux中已經不再是必須的了。如果應用需要移植到其他UNIX系統中,那麼就應該避免使用這兩個系統呼叫。

9.7.3 Retrieving and Modifying Supplementary Group IDs

getgroups() 系統呼叫返回呼叫程序的所屬組的集合,儲存在grouplist指向的陣列中。

#include <unistd.h>
// 成功時返回grouplist中組ID的個數,失敗時返回-1
int getgroups(int gidsetsize, gid_t grouplist[]);

像大部分UNIX實現一樣,在Linux中,getgroups()僅僅返回呼叫程序的輔助組IDs。然而,SUSv3規定,在返回的grouplist中還可以包含呼叫程序的有效組ID。
呼叫程式必須為grouplist陣列分配記憶體,並在gidsetsize引數中指定grouplist陣列的長度。如果執行成功,getgroups()返回grouplist中存放的group IDs的個數。
如果程序的組的個數超過了gidsetsize,getgroups()將返回EINVLA錯誤。為了避免這種情況,可以將grouplist的長度設為1(為保證可移植性,將有效組ID算上)加上常量NGROUPS_MAX(在<limits.h>中定義)。NGROUPS_MAX中定義了程序的輔助組的最大個數。所以,我們可以這樣宣告grouplist:

gid_t grouplist[NGROUPS_MAX + 1];

在Linux核心2.6.4之前,NGROUPS_MAX的值是32,從核心2.6.4開始,NGROUPS_MAX的值是65535。
應用也可以在執行時,使用以下方式確定NGROUPS_MAX限制:

  • 呼叫sysconf(_SC_NGROUPS_MAX)。(11.2節將解釋sysconf()的用法)
  • 從Linux特有的、只讀的 /proc/sys/kernel/ngroups_max 檔案中讀取這個值。這個檔案是從核心2.6.4才加入的。
    在這裡插入圖片描述

除此之外,應用還可以在呼叫getgroups()時指定gidtsetsize為0。這種情況下,grouplist不會被修改,但是會返回呼叫程序所屬組的個數。
使用上面技術獲得的值都可用於為將來的getgroups()呼叫動態分配grouplist陣列。
特權程序可使用 setgroups()initgroups() 來改變輔助組ID的集合。

#define _BSD_SOURCE
#include <grp.h>
//成功時返回0,失敗時返回-1
int setgroups(size_t gidsetsize, const gid_t *grouplist);
int initgroups(const char *user, gid_t group);

setgroups()系統呼叫使用給定的grouplist陣列替換程序的輔助組IDs。gidsetsiz引數指定了grouplist陣列中組IDs的個數。
initgroups()函式通過掃描/etc/groups,並構建一個user引數所屬組的列表,從而初始化呼叫程序的輔助組IDs。此外,group引數中指定的group ID也會加到程序的輔助組IDs中。
initgroups()主要用於建立登入會話的程式,例如 login程式,它會在執行使用者的login shell時設定各種程序屬性。這類程式一般從 密碼檔案中 讀取相應使用者的 組ID 欄位,將獲取的值作為group引數。這裡稍微有點令人費解 【confusing,混亂】 ,因為因為密碼檔案中的組ID並不是真正的輔助組,而是為login shell定義了初始的實際使用者ID、有效使用者ID和saved set-user-ID。儘管如此,這就是initgroups()函式經常使用的方式。
儘管不是SUSv3中定義的規範,但是所有UNIX實現支援setgroups()和initgroups()。

9.7.4 summary of Calls for Modifying Process Credentials

Table 9-1 對修改程序憑證的各種系統呼叫和庫函式的效果進行了概括。
Figure 9-1 提供了與Table 9-1中相同資訊的圖形概括。本圖內容是從修改使用者ID的角度加以展示的,修改組ID與之類似。
在這裡插入圖片描述
在這裡插入圖片描述

9.7.5 Example: Displaying Process Credentials

Listing 9-1的程式使用上述的系統呼叫和庫函式來獲取程序的所有使用者ID和組ID,然後進行展示:

// Listing 9-1 Dispaly all process user and group IDs
// proccred/idshow.c
#define _GNU_SOURCE
#include <unistd.h>
#include <limits.h>
#include "ugid_functions.h" /* userNameFromId() & groupNameFromId() */
#include "tlpi_hdr.h"
#define SG_SIZE(NGROUPS_MAX + 1)
int main(int argc, char *argv[])
{
    uid_t ruid, euid, suid, fsuid;
    gid_t rgid, egid, sgid, fsgid;
    gid_t suppGroups[SG_SIZE];
    int numGroups, j;
    char *p;

	if (getresuid(&ruid, &euid, &suid) == -1)
		errExit("getresuid");
	if (getresgid(&rgid, &egid, &sgid) == -1)
 		errExit("getresgid");
 	
 	/* Attempts to change the file-system IDs are always ignored
 	for unprivileged processes, but even so, the following
 	calls return the current file-system IDs */
 	fsuid = setfsuid(0);
 	fsgid = setfsgid(0);

	printf("UID: ");
 	p = userNameFromId(ruid);
 	printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long) ruid);
 	p = userNameFromId(euid);
 	printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long) euid);

	p = userNameFromId(suid);
 	printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long) suid);
 	p = userNameFromId(fsuid);
 	printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long) fsuid);
 	printf("\n");

	printf("GID: ");
 	p = groupNameFromId(rgid);
 	printf("real=%s (%ld); ", (p == NULL) ? "???" : p, (long) rgid);

	p = groupNameFromId(egid);
 	printf("eff=%s (%ld); ", (p == NULL) ? "???" : p, (long) egid);
 	p = groupNameFromId(sgid);
 	printf("saved=%s (%ld); ", (p == NULL) ? "???" : p, (long) sgid);
	p = groupNameFromId(fsgid);
 	printf("fs=%s (%ld); ", (p == NULL) ? "???" : p, (long) fsgid);
 	printf("\n");

	numGroups = getgroups(SG_SIZE, suppGroups);
 	if (numGroups == -1)
 		errExit("getgroups");
 	printf("Supplementary groups (%d): ", numGroups);
 	for (j = 0; j < numGroups; j++) {
 		p = groupNameFromId(suppGroups[j]);
 		printf("%s (%ld) ", (p == NULL) ? "???" : p, (long) suppGroups[j]);
 	}
 	printf("\n");
 	exit(EXIT_SUCCESS);
}

9.8 Summary

每個程序都有一些相關的使用者IDs和組IDs(憑證,credentials)。實際IDs定義了程序的所屬權。在大部分UNIX實現中,當訪問像檔案這樣的資源時,有效IDs用於決定程序的許可權。然而,在Linux中,使用檔案系統IDs決定訪問檔案的許可權,而使用有效IDs進行其他許可權檢查。(因為檔案系統IDs通常與對應的有效IDs具有相同的值,所以Linux對檔案許可權的檢查方式與其他UNIX相同 【Linux behaves in the same way as other UNIX implementations when checking file permissions,所以當檢查檔案許可權時,Linux表現出與其他UNIXs實現相同的行為】 )程序的輔助組IDs程序所屬的額外組,用於許可權檢查。不少系統呼叫和庫函式允許程序獲取和改變它的使用者IDs和組IDs。
當執行set-user-ID程式時,程序的有效使用者ID被設定成檔案的所屬者。該機制允許使用者“假借”其他使用者的特權來執行特定的程式。
相應的,set-group-ID程式會改變執行該程式的程序的有效組ID。saved set-user-ID和saved set-group-ID允許set-user-ID程式和set-group-ID程式臨時放棄特權,隨後又可重新獲取。
使用者ID 0是特別的。通常,名為root的使用者帳號具有這個使用者ID。擁有有效使用者ID為0的程序是具有特權的——也就是說,當程序執行各種系統呼叫時,可以免去很多許可權檢查。

相關推薦

9 程序憑證

每個程序都有一組與之相關的數值型使用者識別符號(UIDs)和組識別符號(GIDs)。有時,把這些識別符號稱之為 程序憑證(process credentials) 。這些識別符號有: 實際 【真實】 (real)使用者ID和實際組ID; 有效(effectiv

Linux系統程式設計手冊9-程序憑證

1.各個程序的各種ID,稱為程序憑證。如UID,GID等。具體有實際使用者ID(real user ID), 實際組ID(real group ID),有效使用者ID(effective user ID),有效組ID(effective group ID),儲存

9 定制應用程序外觀

大小 oid [1] window amp kobject black 圖形 \n 參考: https://blog.csdn.net/u014162133/article/details/46573873 1、修改外觀和圖標可以在MainFrm中進行,而修改背景和光標

java編程思想四版9

art new end strac override @override err private over 練習3: public class MainTest { public static void main(String args

9:Shell腳本進程管理

dstat align 性能 主機名 running auto whatis sighup 發行版本 第9章:Shell腳本進程管理 9.1、進程概念 理解進程概念需要先簡單了解指令和程序這兩個概念,進程跟指令和程序是相關聯的。 什麽叫指

accp8.0轉換教材9JQuery相關知識理解與練習

ntb 驗證 單詞 手機號碼 sdn load .com read 要求 自定義動畫 一.單詞部分: ①animate動畫②remove移除③validity有效性 ④required匹配⑤pattern模式 二.預習部分 1.簡述JavaScript事件和jquery事件

9 自動化驗證而不修改需求說明 04

應用 例如 由於 例子 變化 修改 選擇 用戶界面 實用 1,在應用程序的表皮之下進行自動化。拋開UI層,直接對服務或接口層進行自動化的效率要高一些。如果自動化綁定到UI層,則開銷很大,例如采用點擊方式自動化,需要錄制很多點擊事件,成本太高。 2,自動化選擇哪些內容也比較重

9 應用層(4)_超文本傳輸協議HTTP

span 關閉連接 多圖 帳戶 通過 從服務器 -668 傳輸協議 分享 5. 超文本傳輸協議HTTP 5.1 統一資源定位符URL (1)URL的一般形式:<協議>://<主機>:<端口>/<路徑>   ①協議後面必須寫上“

9 應用層(5)_文件傳輸協議FTP

public 被動模式 更多 保留 允許 服務器端 磁盤 stat 命令 6. 文件傳輸協議FTP 6.1 FTP主動和被動模式 (1)FTP協議   ①與其他協議不同,FTP協議在客戶端訪問FTP服務器時需要建立兩個TCP連接。一個用來傳輸FTP命令,一個用來傳輸數據。

《.NET 設計規範》 9 :常用的設計模式

負責 工廠 tag var process api 實例 sco 允許 第 9 章:常用的設計模式 9.1 聚合組件   考慮為常用的特性域提供聚合組件。   要用聚合組件來對高層的概念(物理對象)進行建模,而不是對系統級的任務進行建模。   要讓聚合組件的名字與眾

9 mysql

python mysqlmysql介紹數據庫系統: 數據庫:文件夾 數據表:文件 表記錄:一條數據數據庫管理軟件 mysql : 開源 oracle sqlservermysql : 服務端: mysqld 客戶端: cmd

《Effective Java》9 異常

電話 參考資料 輸入 取消 技術 線程停止 調用 個數 表示 第58條:對可恢復的情況使用受檢異常,對編程錯誤使用運行時異常 Java程序設計語言提供了三種可拋出結構(throwable) ;受檢的異常(checked exception)運行時異常(run-time e

數據查詢9

rom 邏輯運算符 rop Language 方法 創建 定義 等於 init 用SQL語句操作數據。 SQL的組成: (1)DML(Data Manipiation Language ,數據操作語言,)用來插入,修改和刪除數據庫中的數據,如:INSERT,UPDATE,D

1 程序設計入門

相關 tcc 使用 pri 實現 std c89 什麽 closed 記錄總結 程序編譯與運行原理與過程 理解算法競賽程序三部曲:輸入、計算、輸出;記住算法競賽的目標及其對程序的要求;保持簡單(Keep It Simple and Stupid, KISS) 附錄A 黑盒

【閱讀筆記】《C程序員 從校園到職場》 程序的樣式(大括號)

突出 char s 結構體 需要 初始化 detail 處理 思維 https 參考: https://blog.csdn.net/zhouzhaoxiong1227/article/details/22820533 一、.初始化數組變量 在實際的軟件開

Linux命令應用大詞典-9 數字計算

9.1 inux log 數字 ont body class 標準輸出 color 9.1 bc:任意精度的計算器 9.2 dc:一個任意精度的計算器 9.3 expr:將表達式的值打印到標準輸出 9.1 bc:任意精度的計算器 9.2 dc:一個任意精度的計算器

Linux命令應用大詞典-12 程序編譯

刪除 font AC 初步 更新 調試器 應用 調試 osc 12.1 gcc:GNU項目的C和C++編譯器 12.2 gdberver:為GNU調試的遠程服務器 12.3 cmake:跨平臺的Makefile生成工具 12.4 indent:更改通過插入或刪除空格的C程

9WEB09-Servlet篇

Servlet篇 javaweb 今日任務? 完成系統的登錄的功能? 完成登錄系統後頁面定時跳轉? 記錄系統登錄成功後,系統被訪問多少次教學導航教學目標了解HTTP協議掌握Servlet的編寫了解ServletConfig的使用掌握ServletContext對象的使用教學方法案例驅動法1.1 上次課

9 文本處理工具sed

sed 文本 linux 筆記整理開始時間:2018年4月17日08:45:48 更多內容請點擊:Linux學習從入門到打死也不放棄,完全筆記整理(持續更新,求收藏,求點贊~~~~) http://blog.51cto.com/13683480/2095439第9章 文本處理工具sed 本章內容

9 網絡應用開發

style python語言 開發網站 支持 跨平臺 開發網頁 網頁爬蟲 Go 標準庫   Socket是計算機之間進行網絡通信的一套接口程序,目前已經成為網絡編程的標準,可以實現跨平臺的數據傳輸。Socket相當於在發送端和接收端之間建立了一個管道來實現數據和命令的相互傳