linux系统学习笔记:无死角理解保存的设置用户ID,设置用户ID位,有效用户ID,实际用户ID
一、基本概念
实际用户ID(RUID):用于标识一个系统中用户是谁,一般是在登录之后,就被唯一的确定,就是登录的用户的uid。
有效用户ID(EUID):用于系统决定用户对系统资源的权限,也就是说当用户做任何一个操作时,最终看它有没有权限,都是在判断有效用户ID是否有权限。如果有,则ok,否则报错不能执行。在正常的情况下,一个用户登录之后(假设是A用户),A用户的有效用户ID和实际用户ID是相同的,但是如果A用户在某些场景中想要执行一些特权操作,能顺利的执行吗?上面说到了用户的任务操作,linux内核都是通过检验有效用户ID来判断当前执行这个操作的用户是否具有权限。这里的A用户想要执行的是特权操作,A用户没有这个权限,所以A用户就只能通过一定的手段来修改当前的有效用户ID使其具有执行特权操作的权限。
这里总结一句话:为什么要修改进程的有效用户ID,就是想在某一时刻能够执行一些特权操作。
设置用户ID位:用于对外的权限的开放,它的作用是修改进行的有效用户ID,给进程赋予临时的特权。
保存的设置用户ID:是有效用户ID副本,既然是有效用户ID的副本,那么它的作用肯定是为了以后恢复有效用户ID。
这里涉及很多的ID,通过下图看一下这些ID都是属于谁:
下面说一下文件设置用户ID位,这个ID仅仅是一个二进制的bit位,在文件stat结构的st_mode成员中,对于一般的文件,该位是置为无效的,只有可执行文件的该位是置为有效的。
二、改变三个用户ID的方法
下面这幅图给出了改变实际用户ID、有效用户ID和保存的设置用户ID的方法
再来一幅图看一下文件的ID和进程的ID在权限访问上的对应关系。对于一个普通文件,有三个ID,这三个ID对应三组权限,这三组权限控制着进程对该文件的访问权限。
在注意一点:ID并不是一个int或者一个标识,他是一个操作系统用户的标号,该用户创建的所有的进程都是这个ID。
对于linux系统,某个用户登录后,创建一个文件,那个这个文件的用户ID就是这个用户的ID。该用户创建的所有的进程都可以访问这个文件,因为该用户创建的进程的实际用户ID和有效用户ID都是这个用户的ID。但是当一个用户创建的进程要去访问其他用户创建的文件的时候,就需要用到有效用户ID的改变,来能够有权限访问这个文件。
1. 进程打开、创建、删除文件时的权限测试?
测试的参与者是进程的有效用户id、有效组id、文件的拥有者id、文件拥有者组id。
(1)进程的有效用户id是0(超级用户),则允许访问;
(2)进程的有效用户id等于文件拥有者id,则按照拥有者的权限访问;
(3)进程有效组id或附加组id之一等于文件拥有者组id,则按照文件拥有者组的权限访问;
(4)否则,按照文件的其他用户访问权限访问;
上述的四步是按续依次进行的。
2. 什么时候用到设置用户id和设置组id?
当进程通过exec函数执行某个文件的时候,而且文件的设置用户id只会影响到进程的有效用 户id
(其实也足够了,因为只有有效用户id参与权限测试),这样说不够严谨,因为进程的保存设置用
户id和保存设置组id会被exec函数从有效用户
id和有效组id复制过来,所以保存的设置用户id和保存的设置组id也会随之改变。
(1)当文件的设置用户id位和设置组id位没有打开:
进程的有效用户id和有效组id保持不变,严格按照第2步进行权限测试;
(2)文件的设置用户id位和设置组id位被打开:
exec函数才会把进程的有效用户id和有效组id设置为文件拥有者的用户id和组id,这时再进
行权限测试,进程就拥有了和文件拥有者一样的访问权限。
3. 进程保存设置用户id和保存设置组id有什么用?
顾名思义,这两个id存在的价值就是保存,保存谁呢?保存有效用户id和有效组id。当进程的
实际用户id和有效用户id不同时(组id同理),保存的设置用户id才有意义。因为这样就可以通
过调用setuid()把有效用户id切换为与进程的实际用户id或保存的设置用户id相同的值,不保存下
来,我们就弄丢了。
三、具体实例分析
实例分析一:
如何在权限不够的情况下执行特权权限,具体的方法就是更改进程的有效用户ID。
对于linux系统来说,用户的密码都存放在/etc/shadow文件下,查看一下这个文件的权限:
假如我是一个普通的用户,显然我是可以修改我自己的密码的,通过passwd命令,无可厚非,自己修改自己的密码当然是允许的。
但是仔细想想有没有什么不对的地方,作为一个普通的用户登录后,我的所有的进程的实际用户ID和有效用户ID都应该是我自己(这个用户)的UID。从上面对/etc/shadow文件用户ID和所属的组ID看,我不具备修改这个文件的权限,那么执行passwd命令是怎么修改我的密码的呢?
根据上面所讲的知识,决定进程对文件的访问权限的是执行操作时的有效用户ID,所以在执行passwd命令时,进行的有效用户ID肯定不是我这个普通用户的UID,一定被修改过了。
还有一点,在执行passwd这个命令的时候,在磁盘上肯定有一个可执行的文件,下面看看这个可执行文件的权限:
我们看到了一个s,这就是使用户设置ID位有效,上面说过这个位的作用就是修改执行这个可执行文件的进程的有效用户ID,那么我么来看看他是怎样修改执行passwd命令的进程的有效用户ID的。
首先看一下命令的执行的过程,当普通用户执行passwd命令时,shell会fork出一个子进程,此时该进程的有效用户ID还是这个普通用户的ID,然后exec程序执行/usr/bin/passwd这个例程(可执行文件)。通过上面的表我们就能知道,exec发现/usr/bin/passwd这个可执行文件有SUID为,于是会把进程的有效用户ID设置成可执行文件的用户ID,显示是root,此时这个进程就获得了root的权限,得到了读写/etc/shadow文件的权限,从而普通用户可以完成密码的修改。exec进程退出后,会恢复普通用户的有效用户ID为实际用户ID(也就是该普通用户的ID),这样就保证了不会是普通用户一直具有root权限。
在exec时,修改进程的有效用户ID为文件的用户ID,还是恢复进程的有效用户ID为进程的实际用户ID,这些都是linux内核完成的,用户是不能干预这些步骤的。
这样的过程既实现了普通用户权限的暂时的提升,不让普通用户长久的拥有root权限。同时即使在普通用户拥有root权限的这段短暂的时间里,普通用户也不能为所欲为,因为普通用户的这个进程必须按照/usr/bin/passwd这个可执行文件的指令内容执行,不能干之外的任何事情,而这个可执行文件又是root事先写好的,多么完美的设计啊~~
这就是设置用户ID为的作用,它的存在就是为了普通用户能够在某些时候获取一段时间的超级权限,但是你可能疑问,为什么不能用setuid直接修改呢?
如果这里可以用setuid直接修改进程的有效用户ID来获得特权权限,那么整个系统的超级权限就不可控制了,这违背了最小权限模型。所以linux的设计是:setuid在非特权用户下面,有效用户ID只能设置成为实际用户ID或者保存的设置用户ID。保存的设置用户ID又是有效用户ID的副本,有效用户ID只能是实际用户ID或者文件的所有者ID(在设置了保存用户ID位时)。
这样你就不能将有效用户ID设置成随意的值,所以对普通用户创建的任何文件,如果没有得到超级用户的授权,那么无论怎么编写代码来设置运行进程的有效用户ID或者设置用户ID位,由于这个可执行文件是普通用户自己编写的,所以权限根本没有任何改变。这里就是说只有root自己创建的可执行文件并且设置了用户ID位,才能够提升执行该可执行文件的进程的权限。
实例分析二:
上面的例子中没有用到保存的设置用户ID,看看这个ID的作用。既然保存的设置用户ID是有效用户ID的副本,那么肯定是为了在某个时刻用于恢复原先的有效用户ID的,这样就能够实现用户权限的切换。
例如:man命令(这是AUP上面的例子,当然实际linux上好像不是这么实现的,不过还是哪这个当做例子说明)
man程序文件的所有者和其它属组通常都是man自身保留的用户和组,man可能需要执行许多其它命令,以处理包含显示手册页的文件,为了防止被欺骗和重写错误文件,man在两种权限之间切换。运行man命令用户的权限,拥有man可执行文件用户的权限。下面列出其工作步骤:
(1)man拥有者是名为man的用户,且设置用户ID位已经设置。当我们exec此程序的时候,用户ID的情况是:
实际用户ID=我们的用户ID 有效用户ID=man //设置用户ID位已经设置,有效用户ID改变 保存的设置用户ID=man
(2)进程访问需要的配置文件和手册页,这些文件由man用户拥有,因为有效用户ID是man,所以可以访问。
(3)man代表我们执行任何一个命令之前,调用setuid(getuid()),我们不是超级用户,所以仅仅改变有效用户ID,现在用户ID的情况变为:
实际用户ID=我们的用户ID(没有改变) 有效用户ID=我们的用户ID 保存的设置用户ID=man
看出来了吧,如果不保存下来,我们就把man用户丢了,再想setuid(man)就不会成功了,因为只允许将有效用户ID设置为与实际用户ID或者保存的设置用户ID相同的值。这时候,我们做回了自己。就只能访问我们通常可以访问的东西,没有特殊的权限了。man代表我们安全执行一次过滤。
(4)执行完过滤后,man再调用setuid(euid)(这里的euid是man的数值用户id,man自己通过geteuid获得并保存)。因为euid等于保存的设置用户ID,设置成功。
我们这时得到:
实际用户ID=我们的用户ID(未改变) 有效用户ID=man 保存的设置用户ID=man(未改变)
(5)因为有效用户ID是man,所以现在可以对其他文件进行操作了。
四、新文件和新目录的所有权
1. 新文件的用户ID设置为进程的有效用户ID
2. 新文件的组ID有两种情况:
- 新文件的组ID为进程的有效组ID
- 如果新文件的组ID所在的目录的设置组ID位设置了的话,新文件的组ID将设为目录的组ID。
3. 其他
- mkdir函数自动的传递了目录的设置组ID位。
- access函数是用设计用户ID和实际组ID来进行访问权限的测试的,如果你只是想测试一下实际用户所拥有的权限,这个函数就可以了。
五、编程应用
1. 在执行exec前后,进程的实际用户ID和实际组ID不变,而有效ID取决于所执行的程序文件是否设置了设置用户ID位和设置组ID位, 如果设置了设置用户ID位,则有效用户ID变成程序文件所有者ID,否则有效用户ID不变。对组ID处理方式与此相同
2. 保存的设置用户ID是由exec复制有效用户ID后得来的,所有说设置用户ID是有效用户ID的副本!
3. 一般设计应用程序时,总是试图使用最小特权模型,依照模型,程序应当只具有为完成任务所需的最小特权.
4. 使用函数:
#include <unistd.h> int setuid(uid_t uid); //设置实际用户ID和有效用户ID,如果进程为超级用户,则会设置实际、有效、保存的设置用户ID int setgid(gid_t gid); //设置实际组ID和有效组ID int setreuid(uid_t ruid, uid_t euid); //交换实际用户ID和有效用户ID int setregid(gid_t rgid, gid_t egid); int seteuid(uid_t uid); //更改有效用户ID int setegid(gid_t gid);
注意:
根据书上来看,特权用户使用这几个函数的时候,都是直接用参数的值来设置实际用户ID或者有效用户ID,这些值都可以是任意的。
例如:setreuid(ruid, euid), 如果是特权用户,则直接设置实际用户ID为ruid,有效用户ID为euid。
但非特权用户就不行了,非特权用户用setuid(),seteuid()则只能将有效有户ID设置为实际用户ID或保存的设置用户ID,如果不是这两个数,设置失败!
对编程应用方面的总结:
非特权用户不能指定任意的有效用户ID, 结合其他几个函数来交换或设置有效用户ID, 与设置用户ID位一起实现权限的控制。