每个进程都有一套标识用户ID(UID)和组(GID)。这些标识ID也可以称作进程凭证。
1. 实际用户ID和实际组ID
进程所属的用户和组称为实际用户ID和实际组ID。登录Shell从 /etc/password
文件中读取到的用户id和组id设置为实际ID和实际组ID。创建新进程时,将从父进程中继承这些ID
2.有效用户ID和有效组ID
当进程执行系统调用时,通过有效用户ID和有效组ID确定能做什么操作(访问权限)。
特权级进程(privileged progress)
: 有效用户ID为0(root用户的id)的进程拥有超级用户的所有权限。某些系统调用只能由 root
用户执行。
默认情况下,有效用户ID、组ID等于实际用户ID、组ID,可以通过两种方式改变。
- 执行系统调用
- 执行设置SUID位的进程时,有效用户ID会设置为可执行文件的所属用户ID。
3. Set-User-ID和Set-Group-ID程序(SUID、SGID)
SUID程序会将进程的有效用户ID设置为可执行文件的所属用户ID,获得所属用户ID的权限。SGID程序对进程有效组ID做类似的操作。
例如:普通用户执行设置了 SUID 的 passwd
程序时,会临时获得 root
权限(因为 passwd
的所有者是 root
),从而修改 /etc/shadow
文件(只有 root
可写)。
3.1 SUID、SGID权限位表示
SUID、SGID权限位分别位于用户、组执行权限位(x)上。
- 若文件没用执行权限,则显示为S(大写)
- 若文件用执行权限,则显示为s(小写)
可通过命令设置SUID和SGID位
chmod u+s file
chmod g+s file
通过命令取消SUID和SGID位
chmod u-s file
chmod g-s file
3.2 SUID、SGID应用
如果一个可执行文件的属主为root,并且设置了SUID权限位,当运行程序时,进程会取得超级用户权限。(需要慎用SUID-root权限,可能存在安全隐患)
也可以利用SUID、SGID机制,将进程有效ID设置为root以外的其他用户。
例如:需要访问一个受保护的文件,创建一个具有该文件访问权限的用户(组)ID,然后再创建一个SUID(SGID)程序,程序执行时,会将进程有效ID设置为保护文件的专用ID,这样无需root用户权限也能访问该文件。
3.3 Linux系统中经常使用的SUID程序
- passwd 用于修改用户密码
- mount和umount 用于加载和卸载文件系统
- su 允许用户使用另一个用户身份执行shell
3.4 linux系统中的SGID程序
- wall 用于向tty组下的所有终端写入一条消息
4. 保存set-user-ID和保存set-group-ID(保存SUID和保存SGID)
有效用户ID、组ID的备份,用于恢复权限。
- 若可执行文件的SUID(SGID)权限位开启,将进程的有效用户ID(组)ID设置为可执行文件的属主。若未设置,则进程的有效用户(组)ID保持不变
- 保存SUID和保存SGID的默认值为对应的有效ID
例如进程的初始ID为real=1000,effective=1000,save=1000,当执行了root用户(uid=0)拥有的SUID程序后,进程的用户ID将发生变化:real=1000,effective=0,save=0。
某些系统调用允许将SUID进程的有效用户ID在实际用户ID和保存SUID之间切换。如此,便能做到提升权限做某些操作,然后恢复权限。
5. 文件系统用户ID和组ID
linux系统特有的用户ID和组ID,用于文件系统权限检查。
通常情况下,文件系统用户ID和组ID的值等于有效用户ID和组ID,并跟随后者改变。只有使用Linux系统特有的两个系统调用(setfsuid
和 setfsgid
)时,文件系统用户ID和组ID的值才有与对应的有效ID不同。
5.1 Linux为何提供文件系统ID
由于历史原因。文件系统ID始见于Linux1.2版本。在该版本内核中,如果进程A 的有效用户ID等于进程B的实际用户ID或有效用户ID,那么A就可以向B发送信号。
例如:Linux NFS服务器在处理客户请求时,需要以适当权限访问文件,但不能修改自身有效ID。
如果NFS将自身有效ID设置为客户进程的有效ID例如(1000),则该进程拥有该UID的权限。如果攻击者发送恶意信号(如 SIGKILL
或 SIGSEGV
)给该进程,可能导致服务崩溃或权限被滥用。
5.1.1 Linux的信号机制
linux的进程可以通过信号与进程通信,甚至强制终止进程。非特权进程只能发送 SIGTERM
等信号给自己的进程,而无权限发送信号给其他用户的进程。
5.1.2 文件系统ID解决避免直接修改有效用户ID
NFS服务器可通过 setfsuid()
系统调用设置FSUID为客户端的UID,从而以该身份访问文件,保持自身进程的有效用户ID不变。
5.1.3 SUSv3信号发送权限规则
- 特权级进程可以向任何进程发送信号
- 以root用户和组运行的init进程是特例,只能接收安装了处理器函数的信号,这是为了防止init进程被误杀。
- 如果发送的实际或有效用户ID与接收进程的实际用户ID和保存SUID匹配,非特权进程也可以向另一个进程发送信号。
由于linux内核2.0开始遵循了SUSv3信号发送规则,文件系统ID特性已经没有必要,为了兼容保留下来。
6. 辅助组ID
辅助组ID是用户所属主组以外的其他组的标识ID。添加多个辅助组,用户可以扩展权限范围,访问多个组的资源。
7. 进程凭证系统调用
7.1 获取实际、有效和保存设置标识
#include <unistd.h>
//获取实际用户id
uid_t getuid(void);
//获取有效用户id
uid_t geteuid(void);
//获取实际组id
gid_t getgid(void);
//获取有效组id
gid_t getegid(void);
7.2 修改有效ID
7.2.1 setuid和setgid
setuid
系统调用可以通过uid参数修改调用进程的有效用户id,也可能修改实际用户ID和保存SUID。系统调用 setgid
对应组ID的修改。
#include <unsitd.h>
//修改有效用户id
int setuid(uid_t uid);
//修改有效组id
int setgid(gid_t gid);
setuid
系统调用规则:
- 对于非特权进程调用而言,只能修改进程的有效用户ID,并且只能修改成相应的实际用户ID或保存SUID。(如果违反该约定则会引发
EPERM
错误)只有在执行SUID程序时,该系统调用才起作用,因为普通程序执行时,进程的实际、有效、和SUID都相同。在不同的UNIX实现中,setuid或setgid系统调用的语义可能不用 ,存在可移植性问题
。 - 特权进程调用时,实际用户ID、有效用户ID和保存SUID都被修改为uid参数的值。修改为非特权ID后无法修改回0(root用户)。
7.2.2 seteuid和setegid
#include<unistd.h>
//修改有效用户id
int seteuid(uid_t euid);
//修改有效组id
int setegid(gid_t egid);
该系统调用遵循以下规则:
- 非特权进程能够将有效ID修改为相应的实际ID或者保存设置ID,但是没有可移植性问题。
- 特权级进程能够将有效ID设置成任意值。如果设置成非特权用户,可以恢复特权。
//获取当前有效UID
euid = geteuid();
//删除特权
if(seteuid(getuid()) == -1)
errExit("set error");
//恢复特权
if(seteuid(euid) == -1)
errExit("set error");
7.2.3 setreuid和setregid
setreuid
系统调用能够修改实际和有效用户ID。setregid
对于组有类似功能
#include <unistd.h>
//修改实际和有效用户ID
int setreuid(uid_t ruid, uid_t euid);
//修改实际和有效组ID
int setregid(gid_t rgid, gid_t egid);
如果不想修改其中一个ID,可以将其设置为-1。
系统调用规则:
- 非特权进程只能将实际用户ID修改为当前实际用户ID或有效用户ID,只能将有效用户ID修改为当前实际用户ID、有效用户ID、或者保存SUID。
- 特权进程能够将两个值修改为任意值。
- 无论是否特权进程,只要满足a)ruid不为-1,b)对有效用户ID所设置的值不同与系统调用之前的实际用户ID。这两个条件之一,就能将保存SUID修改为新的有效用户ID
规则3为SUID程序提供了一个永久放弃特权的方法
setreuid(getuid(),getuid());
SUID-root进程如果需要修改用户ID和组ID,应该先调用 setregid
再调用 setreuid
,避免先调用 setreuid
失去特权后将无法修改组ID。
7.3 getresuid和getresgid
Linux提供两个非标准系统调用,可以或者实际、有效、保存ID
#define _GNU_SOURCE
//获取当前实际用户ID、有效用户ID 和保存SUID
int getresuid(uid_t *ruid,uid_t *euid,uid_t *suid);
//获取当前实际组ID、有效组ID 和保存SGID
int getresgid(gid_t *rgid,gid_t *egid,gid_t *sgid);
7.4 setresuid和setresgid
setresuid
允许修改实际用户ID、有效用户ID 和保存SUID
#define _GNU_SOURCE
#include <unistd.h>
//修改实际用户ID、有效用户ID 和保存SUID
int setresuid(uid_t ruid,uid_t euid,uid_t suid);
//修改当前实际组ID、有效组ID 和保存SGID
int setresgid(gid_t rgid,gid_t egid,gid_t sgid);
若不想修改ID,可以将ID值设置为-1。
setresuid
规则(setresgid
类似):
- 非特权进程能够将实际用户ID、有效用户ID和保存SUID中的任一ID修改为实际用户ID、有效用户ID和保存SUID中的任一当前值。
- 特权进程能够修改实际用户ID、有效用户ID和保存SUID为任意值。
- 不管系统调用是否对其他ID做了修改,总是将文件系统ID设置为有效用户ID的值。
这两个系统调用要么修改请求全部成功,要么全部失败,不会有一个失败别的成功的情况。
这些系统调用会带来可移植性问题
。
7.5 获取和修改文件系统ID
#include<sys/fsuid.h>
//设置或修改文件系统用户ID
int setfsuid(uid_t fsuid);
//设置或修改文件系统组ID
int setfsgid(gid_t fsgid);
这两个系统调用存在一些问题
- 没有相应的系统调用来获取当前文件系统ID
- 不会做错误检查。如果将文件系统ID设置为一个非法值,会被忽略掉,无论是否调用成功,都返回之前的文件系统ID。(这也是一种获取文件系统ID的方案)
这两个系统调用的规则:
- 非特权进程可以将文件系统设置为实际ID、有效ID、文件系统ID、保存ID的当前值。
- 特权用户能够修改为任意值。
若需要考虑程序可移植性,应该避免使用这两个系统调用。
7.6 获取和修改辅助组ID
7.6.1 getgroups
#include <unistd.h>
//获取辅助组ID
int getgroups(int gidsetsize,gid_t grouplist[]);
Linux中仅返回调用进程的辅助组ID,SUSv3规范允许UNIX实现返回结果中包含调用进程的有效组ID。
程序必须为 grouplist
分配存储空间,并使用 gidsetsize
参数指定数组长度。如果调用成功,会将组ID返回 grouplist
中。如果返回数量超出 gidsetsize
,则返回错误(EINVAL
)。
为了避免调用出错,可以将 grouplist
的大小设置为常量 NGROUPS_MAX+1
,该常量定义在 <limits.h>
中。
如果需要在运行时获取 NGROUPS_MAX
的上限值
- 调用
scyscnof(_SC_NGROUPS_MAX)
。 - 从Linux特有的
proc/sys/kernel/ngroups_max
只读文件中读取。
应用程序还可以通过调用 getgroups
时,将 gidsetsize
参数设置为0,返回值中会返回进程属组的数量。
7.6.2 特权进程修改辅助组ID集合
特权进程可以通过 setgroups
和 initgroups
修改辅助组ID集合
#define _BSD_SOURCE
#include <grp.h>
//修改辅助组ID集合
int setgroups(size_t gidsetsize,const gid_t *grouplist);
//根据用户名和主组ID自动读取组文件,设置主组和辅助组
int initgroups(const char *user,gid_t group);
initgroups
函数根据用户名 user
参数和 group
指定的组ID,从 /etc/groups
文件中读取所有组(包括主组和辅助组),添加到当前进程的组列表中。
在登录过程中, login
程序会调用 initgroups
函数并传入两个参数用户名和主组ID(从 /etc/passwd
中读取),然后 initgroups
函数根据用户名和主组ID自动从组文件中查找用户所属的组(包括主组和辅助组),把这些组ID添加到进程的“组列表”中。
setgroups
系统调用能够直接设置进程辅助组ID集合。
虽然未纳入SUSv3规范,但是这两个系统调用所有的UNIX实现都支持。
7.7 进程凭证系统调用小结
接口 | 作用 | 非特权进程 | 特权进程 | 可移植性 |
---|---|---|---|---|
setuid setgid |
修改有效ID | 将有效ID修改为当前实际ID 或保存设置ID |
将实际ID、有效ID和保存设置ID 修改为任一值 |
SUSv3规范支持, 但是BSD都派生系统语义不同 |
seteuid setegid |
修改有效ID | 将有效ID修改为当前实际ID 或保存设置ID |
修改有效ID为任意值 | SUSv3规范支持 |
setreuid(r,e) setregid(r,e) |
修改实际ID或有效ID | 将实际ID修改为当前实际ID或有效ID, 将有效ID修改为当前实际ID、有效ID或保存设置ID |
将实际ID和有效ID修改为任意值 | SUSv3规范支持,但操作系统实现不同 |
setresuid(r,e,s) setresgid(r,e,s) |
修改实际ID、有效ID和保存设置ID | 将实际用D、有效ID和保存设置ID中的任一ID 修改为实际ID、有效ID和保存设置ID中的任一当前值 |
将实际用D、有效ID和保存设置ID修改为任意值 | 有可移植性问题 |
sestfsuid setfsgid |
修改文件系统ID | 将文件系统ID修改为实际ID、有效ID或者保存设置ID | 修改文件系统ID为任意值 | Linux系统特有 |
setgroups(n,l) | 修改辅助组ID集合 | 无法调用 | 修改辅助组ID集合任意值 | 所有UNINX实现都支持 |
8. 参考资料
《Linux-UNIX系统编程手册》