【TLPI读书笔记】 九、进程凭证
实际用户ID和实际组ID
实际用户ID和实际组ID确定了进程所属的用户和组。
- 登录shell会从
/etc/passwd
文件中读取相应用户密码记录的第三字段和第四字段,置为其实际用户ID和实际组ID - 当创建新进程时,将从其父进程继承这些ID
有效用户ID和有效组ID
在大多数UNIX实现中,当进程尝试执行各种操作时,将结合有效用户ID、有效组ID,连同辅助组ID一起来确定授予进程的权限。
通常,有效用户ID及组ID与其相应的实际ID相等,但有两种方法能够致使二者不同:
- 使用后面介绍的修改进程凭证的系统调用
- 执行
set-user-ID
和set-group-ID
程序
Set-User-ID
和Set-Group-ID
程序
set-user-ID
程序会将进程的有效用户ID置为可执行文件的用户ID(属主),从而获得常规情况下并不具有的权限。set-group-ID
程序对进程有效组ID实现类似任务。
- 【程序执行步骤1】若可执行文件的
set-user-ID
(set-group-ID
)权限位已开启,则将进程的有效用户(组)ID置为可执行文件的属主。若未设置set-user-ID
(set-group-ID
)权限位,则进程的有效用户(组)ID将保持不变。
保存Set-User-ID
和保存Set-Group-ID
设计保存set-user-ID
(saved set-user-ID
)和保存set-group-ID
(saved set-group-ID
),意在与保存set-user-ID
和保存set-group-ID
程序结合使用。
- 【程序执行步骤2】保存
set-user-ID
和保存set-group-ID
的值由对应的有效ID复制而来。无论正在执行的文件是否设置了set-user-ID
和set-group-ID
权限位,这一复制都将进行。
即该ID在程序开始执行时就会把有效ID保存一份副本,而有效ID除了在程序开始执行时可能改变,程序运行过程中也可改变
有不少系统调用,允许将set-user-ID
程序的有效用户ID在实际用户ID和保存set-user-ID
之间切换。如此一来,程序就能够随时收放自如。
文件系统用户ID和组ID
在Linux系统中,要进行诸如打开文件、改变文件属主、修改文件权限之类的文件系统操作时,决定其操作系统的是文件系统用户ID和组ID(结合辅助组ID),而非有效用户ID和组ID。
- 通常,文件系统用户ID和组ID等同于相应的有效用户ID和组ID,文件系统ID随相应的有效ID改变。只有当使用Linux特有的两个系统调用(
setfsuid()
和setfsgid()
)时,才可以刻意制造出文件系统ID和相应有效ID的不同。 - Linux提供文件系统ID,是历史原因造成的,由于文件系统ID实属异类,且一般都等同于相应的有效ID,所以大部分场景有效ID就可以胜任。
辅助组ID
辅助组ID用于标识进程所属的若干附加的组。
- 新进程从其父进程处继承这些ID
- 登录shell从系统组文件中获取其辅助的组ID
获取和修改进程凭证
获取和修改实际、有效和保存设置标识
获取实际和有效ID
系统调用getuid()
和getgid()
分别返回调用进程的实际用户ID和组ID;而系统调用geteuid()
和getegid()
则对进程的有效ID实现类似功能。#include <unistd.h> uid_t getuid(void); Returns real user ID of calling process uid_t geteuid(void); Returns effective user ID of calling process gid_t getgid(void); Returns real group ID of calling process gid_t getegid(void); Returns effective froup ID of calling process
修改有效ID
setuid()
系统调用以给定的uid参数值来修改调用进程的有效用户ID,也可能修改实际用户ID和保存set-user-ID
;系统调用setgid()
则对相应组实现了类似功能。#include <unistd.h> int setuid(uid_t uid); int setgid(gid_t gid); Both return 0 on success, or -1 on error
适用于
setuid()
系统调用的规则如下:- 当非特权进程调用
setuid()
时,仅能修改进程的有效用户ID。而且仅能将有效用户ID修改成相应的实际用户ID或保存set-user-ID
。setgid()
类似。 - 当特权进程以一个非0参数调用
setuid()
时,其实际用户ID、有效用户ID和保存set-user-ID
均被置为uid参数所指定的值。此操作是单向的。setgid()
类似,但不会引起特权的丢失,即操作是双向的。
进程能够使用
seteuid()
来修改有效用户ID(改为参数 euid 所指定的值),还能使用setegid()
来修改其有效组ID(改为参数 egid 所指定的值)。#include <unistd.h> int seteuid(uid_t, euid); int setegid(gid_t, egid); Both return 0 on success, or -1 on error
进程使用
seteuid()
和setegid()
来修改其有效ID时,会遵循以下规则:- 非特权级进程仅能将其有效ID修改为相应的实际ID或者保存设置ID。
- 特权级进程能够将其有效ID修改为任意值。(若失去特权,可根据规则1来恢复特权)
对于需要对特权收放自如的
set-user-ID
和set-group-ID
程序,更推荐使用seteuid()
。- 当非特权进程调用
修改实际ID和有效ID
setreuid()
系统调用允许调用进程独立修改其实际和有效用户ID。setregid()
系统调用对实际和有效组ID实现了类似功能。#include <unistd.h> int setreuid(uid_t ruid, uid_t euid); int setregid(gid_t rgid, gid_t egid); Both return 0 on success, or -1 on error
参数
ruid
:实际用户IDeuid
:有效用户IDrgid
:实际组IDegid
:有效组ID
参数设为
-1
可忽略设置指定ID返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
同上,这些函数也有一定的规则,下面以
setreuid()
说明:- 非特权进程只能将其实际用户ID设置为当前实际用户ID值(即保持不变)或有效用户ID值,且只能将有效用户ID设置为当前实际用户ID、有效用户ID(即无变化)或保存
set-user-ID
- 特权级进程能够设置其实际用户ID和有效用户ID为任意值
不管进程拥有特权与否,只要如下条件之一成立,就能将保存
set-user-ID
设置成(新的)有效用户ID:ruid
不为-1- 对有效用户ID所设置的值不同于系统调用之前的实际用户ID
获取实际、有效和保存设置ID
在大多数UNIX实现中,进程不能直接获取(或修改)其保存set-user-ID
和保存set-group-ID
的值。Linux提供了两个(非标准)系统调用来实现此功能:getresuid()
和getresgid()
#define _GNU_SOURCE #include <unistd.h> int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid); int getresgid(uid_t *rgid, uid_t *egid, uid_t *sgid); Both return 0 on success, or -1 on error
getresuid()
系统调用将调用进程的当前实际用户ID、有效用户ID和保存set-user-ID
返回至给定的3个参数所指定的位置。getresgid()
系统调用针对相应的组ID实现了类似功能。修改实际、有效和保存设置ID
setresuid()
系统调用允许调用进程独立修改其3个用户ID值。每个用户ID的新值由系统调用的3个参数给定。setresgid()
系统调用对相应的组ID实现了类似功能。#define _GNU_SOURCE #include <unistd.h> int setresuid(uid_t ruid, uid_t euid, uid_t suid); int setresgid(gid_t rgid, gid_t egid, gid_t sgid); Both return 0 on success, or -1 on error
参数
ruid
:实际用户IDeuid
:有效用户IDrgid
:实际组IDegid
:有效组IDsuid
:保存set-user-ID
sgid
:保存set-group-ID
参数设为
-1
可忽略设置指定ID返回值
- 成功:返回
0
- 失败:返回
-1
- 成功:返回
同上,这些函数也有一定的规则,下面以
setresuid()
说明:- 非特权进程能够将实际用户ID、有效用户ID和保存
set-user-ID
中的任一ID设置为实际用户ID、有效用户ID或保存set-user-ID
之中的任意值 - 特权级进程能够对其实际用户ID、有效用户ID和保存
set-user-ID
做任意设置 - 不管系统调用是否对其他ID做了任何改动,总是将文件系统用户ID设置为与有效用户ID(可能是新值)相同
本章修改多个ID的系统调用都遵循:要么全部修改成功,要么全部失败
获取和修改文件系统ID
前述所有修改进程有效用户ID或组ID的系统调用总是会修改相应的文件系统ID。要想独立于有效ID而修改文件系统ID,必须使用Linux特有的系统调用:setfsuid()
和setfsgid()
#include <sys/fsuid.h>
int setfsuid(uid_t fsuid);
Always returns the previous file-system user ID
int setfsgid(gid_t fsgid);
Always returns the previous file-system group ID
setfsuid()
系统调用将进程文件系统用户ID修改为参数fsuid
所指定的值;setfsgid()
系统调用将进程文件系统组ID修改为参数fsgid
所指定的值。
同样,此类变更也存在一些规则,下面以setfsuid()
为例:
- 非特权进程能够将文件系统用户ID设置为实际用户ID、有效用户ID、文件系统用户ID(即保持不变)或保存
set-user-ID
的当前值 - 特权级进程能够将文件系统用户ID设置为任意值
获取和修改辅助组ID
getgroups
系统调用会将当前进程所属组的集合返回至由参数grouplist
指向的数组中
#include <unistd.h>
int getgroups(int gidsetsize, gid_t grouplist[]);
Returns number of group IDs placed in grouplist on success, or -1 on error
参数
gidsetsize
:在该参数中指定其长度grouplist
:指定存储集合的内存地址,调用进程必须负责为grouplist
数组分配存储空间
返回值
- 成功:返回置于
grouplist
中的组ID数量 - 失败:返回
-1
- 成功:返回置于
特权级进程能够使用setgroups()
和initgroups()
来修改其辅助组ID集合
#define _BSD_SOURCE
#include <grp.h>
int setgroups(size_t gidsetsize, const gid_t *grouplist);
int initgroups(const char *user, gid_t group);
Both return 0 on success, or -1 on error
setgroups()
系统调用用grouplist
数组所指定的集合来替换调用进程的辅助组ID。参数gidsetsize
指定了置于参数grouplist
数组中的组ID数量。initgroups()
函数将扫描/etc/groups
文件,为user
创建属组列表。以此来初始化调用进程的辅助组ID。另外,也会将参数group指定的组ID追加到进程辅助组ID的集合中。
总结
本章介绍了进程凭证的概念,通俗一点说就是进程的各种权限,需要理解各ID之间的区别和关系,然后介绍了获取和修改进程凭证的系统调用。
本章中提到的
set-user-ID
和set-group-ID
程序,在shell中经常这么设置:# chmod u+s filename // set-user-ID,即Set UID, SUID # chmod g+s filename // set-group-ID,即Set GID, SGID # chmod o+t dirname // Sticky Bit,粘滞位,即SBIT;该权限只对目录有效,作用为该目录下的文件用户只能删除自己的而不能删除其他人的;本章没有涉及这个权限,只是顺带引出
该类程序都有执行权限,带有此特征的常见命令有:
passwd(1)
、mount(8)
、umount(8)
、su(1)
等等使 有效用户ID 在 实际用户ID 和 保存
set-user-ID
之间来回切换的例子:euid = geteuid(); //Save initial effective user ID (which is same as saved set-user-ID) if (seteuid(getuid()) == -1) //Drop privileges errExit("seteuid"); if (seteuid(euid) == -1) //Regain privileges errExit("seteuid");