接前一篇文章:Linux內核進程管理子系統有什么第四十五回 —— 進程主結構詳解(41)
?
本文內容參考:
Linux內核進程管理專題報告_linux rseq-CSDN博客
《趣談Linux操作系統 核心原理篇:第三部分 進程管理》—— 劉超
《圖解Linux內核?基于6.x》 —— 姜亞華 機械工業出版社
setuid系統調用及示例-CSDN博客
setuid函數解析 - HelloMarsMan - 博客園
特此致謝!
?
進程管理核心結構 —— task_struct
8. 進程權限相關成員
進程權限相關成員包括以下幾個:
?? /* Process credentials: *//* Tracer's credentials at attach: */const struct cred __rcu *ptracer_cred;/* Objective and real subjective task credentials (COW): */const struct cred __rcu *real_cred;/* Effective (overridable) subjective task credentials (COW): */const struct cred __rcu *cred;
這幾個字段的描述如下:
?
上一回繼續對于struct cred進行解析,本回仍然繼續。為了便于理解和回顧,再次貼出struct cred的定義,在include/linux/cred.h中,如下:
/** The security context of a task** The parts of the context break down into two categories:** (1) The objective context of a task. These parts are used when some other* task is attempting to affect this one.** (2) The subjective context. These details are used when the task is acting* upon another object, be that a file, a task, a key or whatever.** Note that some members of this structure belong to both categories - the* LSM security pointer for instance.** A task has two security pointers. task->real_cred points to the objective* context that defines that task's actual details. The objective part of this* context is used whenever that task is acted upon.** task->cred points to the subjective context that defines the details of how* that task is going to act upon another object. This may be overridden* temporarily to point to another security context, but normally points to the* same context as task->real_cred.*/
struct cred {atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALSatomic_t subscribers; /* number of processes subscribed */void *put_addr;unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endifkuid_t uid; /* real UID of the task */kgid_t gid; /* real GID of the task */kuid_t suid; /* saved UID of the task */kgid_t sgid; /* saved GID of the task */kuid_t euid; /* effective UID of the task */kgid_t egid; /* effective GID of the task */kuid_t fsuid; /* UID for VFS ops */kgid_t fsgid; /* GID for VFS ops */unsigned securebits; /* SUID-less security management */kernel_cap_t cap_inheritable; /* caps our children can inherit */kernel_cap_t cap_permitted; /* caps we're permitted */kernel_cap_t cap_effective; /* caps we can actually use */kernel_cap_t cap_bset; /* capability bounding set */kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYSunsigned char jit_keyring; /* default keyring to attach requested* keys to */struct key *session_keyring; /* keyring inherited over fork */struct key *process_keyring; /* keyring private to this process */struct key *thread_keyring; /* keyring private to this thread */struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITYvoid *security; /* LSM security */
#endifstruct user_struct *user; /* real user ID subscription */struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */struct ucounts *ucounts;struct group_info *group_info; /* supplementary groups for euid/fsgid *//* RCU deletion */union {int non_rcu; /* Can we skip RCU deletion? */struct rcu_head rcu; /* RCU deletion hook */};
} __randomize_layout;
前邊幾回結合例程,講解setuid在不同權限下的行為。分別講解了:
(1)調用者是特權用戶(root)的情況
(2)調用者是普通用戶(程序文件擁有者)的情況
(3)調用者是普通用戶(非程序文件擁有者)的情況。
上一回筆者說雖然setuid系統調用的各種情況都講了,但并未講到真正的精髓。本回就來講講這個精髓之處。為了便于理解和回顧,再次貼出例程代碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#define _GNU_SOURCE
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>void show_curr_uids(void)
{int ret;uid_t real_uid;uid_t effective_uid;uid_t saved_uid;ret = getresuid(&real_uid, &effective_uid, &saved_uid);printf("Real uid: %d, Effective uid: %d\n", real_uid, effective_uid);printf("Current UIDs:\n");printf("\tReal uid: %d\n\tEffective uid: %d\n\tSaved uid: %d\n", getuid(), geteuid(), (getresuid(&real_uid, &effective_uid, &saved_uid) == 0) ? saved_uid : -1);
}int main(int argc, char *argv[])
{int ret;uid_t original_ruid, original_euid, original_suid;uid_t target_uid;//獲取并打印初始 UIDif (getresuid(&original_ruid, &original_euid, &original_suid) != 0){perror("getresuid");exit(EXIT_FAILURE);}//檢查是否以root權限運行if (geteuid() == 0){printf("\nRunning as ROOT (Privileged User)\n");printf("Before setuid.\n");show_curr_uids();//作為root,可以設置為任意有效的UID//這里我們嘗試設置為'nobody' 用戶 (通常UID 65534, 但請檢查你的系統)target_uid = 65534;printf("\nAttempting to set UID to %d (usually 'nobody' user)...\n", target_uid);ret = setuid(target_uid);if (ret == 0){printf("setuid(%d) succeeded.\n", target_uid);printf("\nAfter setuid.\n");show_curr_uids();printf("Note: All UIDs (Real, Effective, Saved) are now %d.\n", target_uid);printf("The process is now running with 'nobody' privileges.\n");}else{perror("setuid");printf("Failed to set UID to %d.\n", target_uid);}}else{printf("\nRunning as a REGULAR USER (UID: %d)\n", getuid());printf("Before setuid.\n");show_curr_uids();//作為普通用戶,只能設置為自己的ruid或suidtarget_uid = original_ruid; //選擇設置為自己的真實UID (這不會改變任何東西)printf("\nAttempting to set UID to my Real UID (%d)...\n", target_uid);ret = setuid(target_uid);if (ret == 0){printf("setuid(%d) succeeded (as expected).\n", target_uid);printf("\nAfter setuid.\n");show_curr_uids();}else{perror("setuid");printf("Failed to set UID to %d.\n", target_uid);}#if 0//嘗試設置為一個無效的UID(比如一個不存在的或不屬于我的UID)//這通常會失敗target_uid = 9999; //假設這是一個無效的或不屬于當前用戶的UIDprintf("\nAttempting to set UID to an invalid/different UID (%d)...\n", target_uid);ret = setuid(target_uid);if (ret == -1){if (errno == EPERM){printf("setuid(%d) failed with EPERM (Operation not permitted) - as expected for a regular user.\n", target_uid);printf("This is because %d is not my Real UID (%d) or Saved Set-UID (%d).\n", target_uid, original_ruid, original_suid);}else{perror("setuid");printf("Failed to set UID to %d.\n", target_uid);}printf("After failed setuid.\n");show_curr_uids();}else{printf("setuid(%d) unexpectedly succeeded.\n", target_uid);printf("After unexpected setuid.\n");show_curr_uids();}
#endif}return 0;
}
這次只關注代碼中else分支的前半段,也就是:
//檢查是否以root權限運行if (geteuid() == 0){……}else{printf("\nRunning as a REGULAR USER (UID: %d)\n", getuid());printf("Before setuid.\n");show_curr_uids();//作為普通用戶,只能設置為自己的ruid或suidtarget_uid = original_ruid; //選擇設置為自己的真實UID (這不會改變任何東西)printf("\nAttempting to set UID to my Real UID (%d)...\n", target_uid);ret = setuid(target_uid);if (ret == 0){printf("setuid(%d) succeeded (as expected).\n", target_uid);printf("\nAfter setuid.\n");show_curr_uids();}else{perror("setuid");printf("Failed to set UID to %d.\n", target_uid);}#if 0……
#endif}
這次要做的實驗步驟如下:
1)首先,通過chmod u+s /tmp/setuid_example命令,對setuid_example添加上SUID權限
實際命令及結果如下:
?
2)使用shiyan用戶(UID為1001)來運行setuid_example程序
這個程序的實際用戶ID(real UID)為1001,而有效用戶ID(effective UID)和保存的設置用戶ID(saved UID)都為1000。接著,在這個程序中調用setuid函數,傳入的參數為1001,大家可以猜想到,最終應該只有有效用戶ID受到影響(會從1000變為1001),而保存的設置用戶ID保持不變。
實測結果如下:
?
實測結果與預期一致。
?
?
再來對比一下沒有使用chmod u+s /tmp/setuid_example命令之前的結果:
?
也對比一下示例代碼中將target_uid(uid)設置為1000(用戶ph的UID)的結果:
?
雖然上面的示例在運行時調用setuid,但setuid系統調用的強大之處還體現在可執行文件的SUID位上。這就要和chmod u+s命令結合起來,共同起作用了。
當將一個可執行文件的SUID位設置為1時(如通過chmod u+s xxx),會發生以下情況:
無論哪個用戶執行這個文件,該進程啟動時的有效用戶ID(EUID)均會被設置為該文件所有者的用戶ID。這使得普通用戶可以運行一個具有程序文件所有者權限的程序。
這一點從上邊的結果就可以看出來,沒有執行chmod u+s /tmp/setuid_example之前,Effective UID是1001,也就是用戶shiyan;
執行chmod u+s /tmp/setuid_example后,Effective UID是1000,也就是setuid_example的屬主ph了。
為了更好地理解setuid和chmod u+s,這里再給出一個例子:
至此,struct cred的前4組字段就解析完了。下一回繼續解析后續字段。
?