文章目录
简介
与进程相关的id | 简介 | 查看方式 | 如何设置 |
---|---|---|---|
实际用户ID (Real User ID, RUID) | 进程被创建时的用户ID | ps、cat /proc/PID/status、getuid() | 一般不可更改 |
实际组ID (Real Group ID, RGID) | 进程被创建时的组ID | ps、cat /proc/PID/status、getgid() | 一般不可更改 |
有效用户ID (Effective User ID, EUID) | 进程访问文件或其他资源时使用的用户ID | ps、cat /proc/PID/status、geteuid()、getreuid()、getresuid() | setuid()、seteuid()、setreuid()、setresuid() |
有效组ID (Effective Group ID, EGID) | 进程访问文件或其他资源时使用的组ID | ps、cat /proc/PID/status、getegid()、getregid()、getresgid() | setuid()、setegid()、setregid()、setresgid() |
保存的设置用户ID (saved set-user-ID,saved SUID) | 程序文件特殊权限,运行时设置EUID | ls、ps、cat /proc/PID/status、getresuid() | chmod u+s、getresuid() |
保存的设置组ID (saved set-group-ID,saved SGID) | 程序文件特殊权限,运行时设置EGID | ls、ps、cat /proc/PID/status、getresgid() | chmod g+s、getresgid() |
Linux中添加“组”的概念是为了什么?
- 进行访问控制。通过设置文件或进程的组权限,可以控制哪些用户组有权访问它们。
- 实现资源共享。同一个组的用户可以共享文件或其他资源的访问权限。
- 简化权限管理。通过设置组,可以一次性地给多用户设置权限,简化管理。
- 兼容老系统。大多数老系统都有组的概念,Linux为了兼容也继承了这一设计。
- 隔离安全域。设置不同的用户组,可以将系统资源隔离到不同的安全域中。
- 账户跟踪(Accounting Tracking),按用户组记录和跟踪系统资源占用情况。
所以接下来的讲述中,用户ID与组的操作方法大都类似,就不分别展示了。
一、实际用户ID和实际组ID
用于标识进程所属的用户身份。RUID与RGID是进程创建时绑定,并且在进程生命周期内保持不变。
1、RUID是指登录当前会话的用户id吗?如何证明?
不完全正确。证明如下:
# 以用户root登录, 查看当前RUID
[root@localhost ~]# id
uid=0(root) gid=0(root) 组=0(root)
# 添加新用户newone, 并登录, 查看RUID
[root@localhost ~]# useradd newone
[root@localhost ~]# su newone
[newone@localhost root]$ id
uid=1000(newone) gid=1000(newone) 组=1000(newone)
# 使用newone启动一个后台进程, 并退出
[newone@localhost root]$ sleep 1000 &
[1] 168778
[newone@localhost root]$ exit
# 查看sleep进程的RUID为newone id
[root@localhost ~]# ps -p 168778 -eo pid,ruid,rgid,comm | grep -e "sleep" -e "PID"
PID RUID RGID COMMAND
168778 1000 1000 sleep
因此,一个没有终端的系统进程,它的RUID不与任何登录会话对应,会话结束后,原有进程的RUID也不会改变。
2、如何查看RUID、RGID
# 方法1
ps -eo ruid,rgid,euid,egid,suid,sgid,pid,comm
# 如需要指定进程id,则使用以下命令
ps -eo ruid,rgid,euid,egid,suid,sgid,pid,comm -q 3141
RUID RGID EUID EGID SUID SGID PID COMMAND
0 0 0 0 0 0 3141 SimuService
# 方法2
cat /proc/self/status | grep -e Uid -e Gid
# 如需要指定进程id,则使用以下命令
cat /proc/PID/status | grep -e Uid -e Gid
Uid: 0 0 0 0
Gid: 0 0 0 0
# Uid、Gid每列表示的信息为:
# Real, effective, saved set, and filesystem UIDs (GIDs).
# 其中filesystem UID(文件系统用户ID),是指分配给每个用户目录的用户ID。
# 每个用户目录都拥有一个唯一的用户ID和组ID。这个用户ID被称为文件系统用户ID。
# 当一个用户登录系统时,会将当前用户的EUID设置为文件系统用户ID。这样用户就可以访问自己家目录中的文件。
# 文件系统中的每个文件和目录都保存了用户ID和组ID。读取文件时会检查用户的有效ID是否匹配文件所有者ID,以确定访问权限。
# 方法3,系统调用getuid()/getgid(),下文中展示
二、有效用户ID和有效组ID
进程用EUID
来决定我们对资源的访问权限。一般情况下,EUID
等于RUID
,EGID
等于RGID
。但有效用户ID不是一成不变的,它是可以更改的,有效用户组ID也一样。
1、如何更改EUID?
查看EUID的方法前面已经展示了。更改EUID的方法有两种:第一种方法是通过系统调用修改;第二种方法则是通过修改保存设置用户ID,在第3节中讲述。
为什么要修改进程的有效用户ID? 就是想在某一时刻能够执行一些特权操作。
2、setuid()
#include <sys/types.h>
#include <unistd.h>
int setuid(uid_t uid);
setuid()设置调用进程的有效用户ID。如果调用进程具有权限,更准确地说:如果该进程在其user namespace
(用户命名空间,用于隔离用户环境)中具有CAP_SETUID
功能,那么还将同时设置RUID
和SUID
。
不是说RUID是不可以更改的吗?怎么还能通过系统调用去设置?
进程的RUID
在进程生命周期内原则上是不可更改的。但是root可以修改RUID
的值。举例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#define PRINT_ID() {printf("RUID:%-4d\t EUID:%-4d\t RGID:%-4d\t EGID:%-4d\n", getuid(), geteuid(), getgid(), getegid());}
int main()
{
PRINT_ID();
setuid(300);
PRINT_ID();
return 0;
}
使用newone用户(属于root组,但无root权限),输出结果如下:
[newone@KL138 ~]$ ./a.out
RUID:1000 EUID:1000 RGID:0 EGID:0
RUID:1000 EUID:1000 RGID:0 EGID:0
无法改变EUID
与RUID
。
使用root用户,输出结果如下:
[root@KL138 newone]# ./a.out
RUID:0 EUID:0 RGID:0 EGID:0
RUID:0 EUID:0 RGID:0 EGID:0
成功改变了EUID
与RUID
。
3、seteuid()
#include <sys/types.h>
#include <unistd.h>
int seteuid(uid_t euid);
-
若进程具有超级用户权限,则seteuid将
RUID
、EUID
及SUID
设置为euid
; -
若进程没有超级用户权限,但是
euid
等于RUID
或SUID
,则seteuid()只将EUID
设置为euid
。
4、setreuid()
用户交换实际用户ID和有效用户ID。
#include <unistd.h>
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
5、setresuid()
用来同时设置进程的RUID
、EUID
和SUID
。
#include <unistd.h>
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
// 设置为root权限
setresuid(0, 0, 0);
// 恢复调用者权限
setresuid(-1, -1, -1);
6、getuid()、geteuid()、getresuid()、getgid()、getegid()、getresgid()
#include <unistd.h>
uid_t getuid(void); // 返回:调用进程的实际用户ID
uid_t geteuid(void); // 返回:调用进程的有效用户ID
uid_t getresuid(void); // 返回:调用进程的设置用户ID
gid_t getgid(void); // 返回:调用进程的实际组ID
gid_t getegid(void); // 返回:调用进程的有效组ID
gid_t getresgid(void); // 返回:调用进程的有效组ID
// 这些函数都没有出错返回
三、保存的设置用户ID和保存的设置组ID
saved set-user-ID
,顾名思义,这两个id存在的价值就是保存,保存谁呢?保存EUID
和EGID
。当进程的RUID
和EUID
不同时(组id同理),SUID
才有意义。因为这样就可以通过调用setuid()把EUID
切换为与进程的RUID
或SUID
相同的值,不保存下来,我们就弄丢了。它的作用就是为了恢复EUID、EGID。
文件属性中有一个特殊标志位s
,当该标志设置时,执行该文件时的EUID就是文件的所有者ID。如下:
当文件的SUID
位设置时,EUID
等于文件的所有者UID,而不是RUID
;同样,如果设置了SGID
,则EGID
等于文件所有者GID,而不是RGID
。
1、如何更改SUID、SGID
- 方法一
chmod u+s a.out
chmod g+s a.out
- 方法二
#include <unistd.h>
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
setresuid(0, 0, 0);
2、举例
对于linux系统来说,用户的密码都存放在/etc/shadow
文件下,假如我是一个普通的用户,显然我是可以修改我自己的密码的,通过passwd命令,无可厚非,自己修改自己的密码当然是允许的。
但是仔细想想有没有什么不对的地方,作为一个普通的用户登录后,我的所有的进程的RUID和EUID都应该是我自己(这个用户)的UID。从上面对/etc/shadow
文件用户ID和所属的组ID看,我不具备修改这个文件的权限,那么执行passwd
命令是怎么修改我的密码的呢?
根据上面所讲的知识,决定进程对文件的访问权限的是执行操作时的EUID
,所以在执行passwd
命令时,EUID
一定被修改过了。还有一点,在执行passwd
这个命令的时候,在磁盘上肯定有一个可执行的文件,设置了s
权限,上面说过这个位的作用就是修改执行这个可执行文件的进程的EUID
,那么我么来看看他是怎样修改EUID
的。
首先看一下命令的执行的过程,当普通用户执行passwd
命令时,shell会fork
出一个子进程,此时该进程的EUID
还是这个普通用户的ID,然后exec
程序执行/usr/bin/passwd
这个例程(可执行文件)。通过上面的表我们就能知道,exec
发现/usr/bin/passwd
这个可执行文件有S
位,于是会把进程的EUID
设置成可执行文件的用户ID,显示是root
,此时这个进程就获得了root
的权限,得到了读写/etc/shadow
文件的权限,从而普通用户可以完成密码的修改。exec
进程退出后,会恢复普通用户的EUID
为RUID
(也就是该普通用户的ID),这样就保证了不会是普通用户一直具有root
权限。
在exec
时,修改进程的EUID
为文件的RUID
,还是恢复进程的EUID
为进程的RUID
,这些都是linux内核完成的,用户是不能干预这些步骤的。这样的过程既实现了**普通用户权限的暂时的提升**,不让普通用户长久的拥有root
权限。
但是你可能疑问,为什么不能用setuid()直接修改SUID
呢?
如果这里可以用setuid()直接修改进程的EUID
来获得特权权限,那么整个系统的超级权限就不可控制了,这违背了最小权限模型。所以linux的设计是:setuid()在非特权用户下面,EUID
只能设置成为RUID
或者SUID
。SUID
又是EUID
的副本,EUID
只能是RUID
或者文件的所有者ID(在设置了保存用户ID位时)。这样你就不能将有EUID
设置成随意的值。
所以对普通用户创建的任何文件,如果没有得到超级用户的授权,那么无论怎么编写代码来设置运行进程的EUID
或者SUID
,由于这个可执行文件是普通用户自己编写的,所以权限根本没有任何改变。这就是说只有root自己创建的可执行文件并且设置了s
位,才能够提升执行该可执行文件的进程的权限。