实际用户ID 有效用户ID 保存设置用户ID

来源:互联网 发布:windows 10怎么用 编辑:程序博客网 时间:2024/04/30 17:42
实际用户ID,有效用户ID与保存设置用户ID   
前段时间一直没搞明白这几个ID之间的关系,今天看到一篇博文,这才拨云见日,才有所了解了.
real user ID:实际用户ID,指的是进程执行者是谁
effective user
ID:有效用户ID,指进程执行时对文件的访问权限
saved set-user-ID:保存设置用户ID,作为effective
user ID的副本,在执行exec调用时后能重新恢复原来的effectiv user ID.
上面这三个ID是相对于进程而言的.
set-user-ID:设置用户ID,这是相对于文件来说的.设置了set-user-ID位的可执行程序,执行时,进程的effective
user ID与saved set-uesr-ID都为程序文件所属用户的ID,些时real user ID与effective
user
ID就不一定相等了.这类程序称之为SUID程序,这类程序有特殊的用途.典型的例子:passwd程序,ping程序等.
passwd程序是要修改用户密码,此时是要修改/etc/passwd或修改/etc
/shadow文件(有必要时),然而一般用户没有修改这两个文件的权限.passwd程序设置了set-user-ID位的,并且该文件的所有都是
root,所以,一般用户执行时,也具有了root的权限.
ping程序也如此,因为ping程序要产生原始套接字(raw),所以需要有root的权限.然而一般用户之所以能用ping程序,就是因为ping程序的所有都是root用户,并且它设置了set-user-ID位.
为一可执行程序设置set-user-ID位:
[email=pds@FSSR]pds@FSSR[/email]
:~> su root
口令:
FSSR:/home/pds # chown root suid
FSSR:/home/pds # ll suid
-rwxr-xr-x 1 root users 7702 2008-08-10 11:28 suid
FSSR:/home/pds # chmod u+s suid
FSSR:/home/pds # ll suid
-rwsr-xr-x 1 root users 7702 2008-08-10 11:28 suid
FSSR:/home/pds # exit
exit

相对的,没有设置set-user-ID位的可执行程序,称之为非SUID程序,该程序执行时,real user
ID与effective user ID相等.
setuid可以修改real user ID,effective user
ID和saved set-user-ID这三个值,但是要用权限.
这是函数原型:
int setuid(uid_t uid)
1.如果用户(当前调用的用户)有超级用户权限,则real user
ID,effective user ID和saved set-user-ID都将设置为参数uid的值.
2.如果用户没有超级用户权限,仅当参数uid等于real user ID或saved
set-user-ID时,effective user ID被设置为参数uid的值,real user ID和saved
set-user-ID不变;否则返回错误.

 

 

7.6 THE USER ID OF A PROCESS

内核会给每个进程关联两个和进程ID无关的用户ID,一个是真实用户ID,还有一个是有效用户ID或者称为setuid(setuserID)。真实用户ID用于标识由谁为正在运行的进程负责。有效用户ID用于为新创建的文件分配所有权、检查文件访问许可,还用于通过kill系统调用向其它进程发送信号时的许可检查。内核允许一个进程以调用exec一个setuid程序或者显式执行setuid系统调用的方式改变它的有效用户ID。

所谓setuid程序是指一个设置了许可模式字段中的setuidbit的可执行文件。当一个进程exec一个setuid程序的时候,内核会把进程表以及u区中的有效用户ID设置成该文件所有者的ID。为了区分这两个字段,我们把进程表中的那个字段称作保存用户ID。可以通过一个例子来演示这两个字段的区别。

setuid系统调用的语法是 setuid(uid),其中,uid是新的用户ID,该系统调用的结果取决于有效用户ID的当前值。如果调用进程的有效用户ID是超级用户,内核会把进程表以及u区中的真实和有效用户ID都设置成uid。如果调用进程的有效用户ID不是超级用户,仅当uid等于真实用户ID或保存用户ID时,内核才会把u区中的有效用户ID设置成uid。否则,该系统调用将返回错误。一般来说,一个进程会在fork系统调用期间从父进程那儿继承它的真实和有效用户ID,这些数值即使经过exec系统调用也会保持不变。

存储在u区中的有效用户ID是最近一次setuid系统调用或是exec一个setuid程序的结果;只有它会被用于文件访问许可。进程表中的保存用户ID使得一个进程可以通过执行setuid系统调用把有效用户ID设置成它的值,以此来恢复最初的有效用户ID。

setuid程序的例子:login,mkdir。[/quote]

 

[quote]Uresh Vahalia 的《UNIX Internals:The NewFrontiers》一书中对这个问题的论述。。。

p27

2.3.3 User Credentials

UID和GID这样的标识符会影响文件的所有权和访问许可,以及向其它进程发送信号的能力。这些属性统称为凭证。

每个进程都有两对ID——真实的和有效的。当一个用户登录的时候,login程序会把两对ID设置成密码数据库(/etc/passwd文件,或某些如SunMicrosystems的NIS之类的分布式机制)中指定的UID和GID。当一个进程fork的时候,子进程将从父进程那儿继承它的凭证。

有效UID和有效GID印象文件的创建和访问。在创建文件的时候,内核将文件的所有者属性设置成创建进程的有效UID和有效GID。在访问文件的时候,内核使用进程的有效UID和GID来判断它是否能够访问该文件。真实UID和真实GID标识进程的真实所有者,会影响到发送信号的权限。对于一个没有超级用户权限的进程来说,仅当它的真实或有效UID于另一个进程的真实UID匹配时它才能向那个进程发送信号。

有三个系统调用可以改变凭证。如果一个进程调用exec执行一个安装为suid模式的程序,内核将把进程的有效UID修改成文件的所有者。同样,如果该程序安装为sgid模式,内核则会去修改调用进程的有效GID。UNIX提供这个特性是想赋予用户特殊的权限以完成一些特定的任务。

一个用户还可以通过调用setuid或setgid来改变它的凭证。超级用户可以通过这些系统调用改变真实的和有效的UID以及GID。普通用户则只能通过这些调用来把它们的有效UID或GID改回到真实的数值。

System V和BSDUNIX在处理凭证方面存在着一些差别。SVR3还维护了一个saved UID和savedGID,分别是在调用exec之前的有效UID和GID的数值。setuid和setgid系统调用还可以把有效ID恢复为保存的数值。4.3BSD不支持这一特性,它允许一个用户属于一个辅组(supplementalgroup)的集合(使用setgroups系统调用)。用户创建的文件将属于它的主组(primarygroup),而用户则既可以访问属于主组的文件,也可以访问属于辅组的文件。

SVR4整合了上述所有特性。它支持附组,也会在exec的时候维护savedUID和GID。

setuid程序的例子:passwd。[/quote]

 

[quote]Marshall Kirk McKusick, George V. Neville-Neil 的《TheDesign and Implementation of the FreeBSD OperatingSystem》一书中对这个问题的论述。。。

3.7 User, Group, and Other Identifiers

每个FreeBSD进程的状态里都有一个UID和一组GID。一个进程的文件系统访问特权就是由它的UID和GIDs来定义的。通常,这些标识符都是新进程创建的时候从父进程那儿自动继承过来的。只有超级用户才能修改一个进程的真实UID或真实GID。这个方案在各种特权之间进行了严格的区分,确保除了超级用户之外的其它任何用户都无法获得特权。

每个文件都有三组许可bit,分别用于所有者、组以及其它用户的读、写或执行许可。这些许可bit将按如下顺序进行检查:
   1、如果文件的UID和进程的UID相同,则仅应用所有者的许可,不再检查组和其它用户的许可。
   2、如果UID不匹配,但文件的GID和进程的众多GID之一匹配,则仅引用组的许可,不再检查所有者和其它用户的许可。
   3、仅当进程UID和GID与文件的UID和GID都不匹配时,才会去检查其它用户的许可。如果这些许可不允许所请求的操作,该操作就会失败。

一个进程的UID和GIDs是从它的父进程那儿继承来的。当一个用户登录的时候,login程序会在执行exec系统调用运行用户的登录shell之前设置好UID和GIDs,因此,后续的所有进程都会继承到恰当的标识符。

我们经常会想赋予一个用户有限的额外特权。......为了解决这个问题,内核允许程序在运行过程中创建被赋予特权的程序。以不同的UID运行的程序被称为setuid程序,以一个额外的组特权运行的程序被称为setgid程序。当运行一个setuid程序的时候,进程的许可将被扩展以包括与程序相关联的UID的许可。该程序的UID就被称为进程的有效UID,而进程最初的UID则被称为真实UID。同样,执行一个setgid程序会把进程的许可扩展为程序的GID的许可,相应的也有有效GID和真实GID的定义。

系统可以通过setuid和setgid程序来提供对文件或服务的受控访问。当然,这样的程序必须仔细编写,以保证它们只具有一些有限的功能。

UID和GIDs是作为每个进程的状态的一部分来维护的。由于历史原因,GIDs被实现成了一个显著的GID(即有效GID)和一个GIDs的辅助数组,不过在逻辑上则被看作是一组GIDs。在FreeBSD中,那个显著的GID就是GIDs数组中的第一个条目。辅助数组的大小是固定的(FreeBSD中是16),不过可以通过重新编译内核来修改这个数值。

FreeBSD是通过把运行setgid程序的进程的辅组数组中的第0个元素设置成文件的属组来实现setgid功能的。之后就可以像普通进程那样对许可进行检查了。由于存在额外的组,setgid程序就能够比一个运行没有特殊权限的程序的用户进程访问更多的文件。为了避免在运行一个setgid程序的时候丢失与第0个数组元素中的组相关联的特权,login程序会在初始化用户的辅组数组的时候将第0个数组元素复制到第一个数组元素中。因此,当运行的setgid程序修改第0个元素的时候,用户不会丢失任何特权,因为曾经保存在第0个数组元素中的组仍然可以从第一个数组元素中得到。

setuid功能是通过把进程的有效UID从用户的数值修改为被运行的程序的数值来实现的。和setgid一样,保护机制此时将毫不变样地允许访问,同时也不会意识到程序正在运行setuid。由于一个进程在同一时刻只能有一个UID,在运行setuid的时候就可能会丢失某些特权。在加载新的有效UID的时候,之前的真实UID将会继续作为真实UID。不过真实UID是不会用于任何确认检查的。

一个setuid进程在运行过程中可能会想临时取消它的特殊权限。比如,它可能只在运行开始和结束的时候需要访问某个受限文件的特殊权限。在其余的运行时间中,它应当只具有真实用户的权限。在BSD的早期版本中,特权的回收是通过对真实的和有效的UID进行切换来完成的。由于只有有效UID被用于访问控制,这个方法既提供了所需的语义,又提供了一个隐藏特殊权限的地方。这个方法的缺点是很容易就混淆了真实的和有效的UID。

在FreeBSD中,使用了一个额外的标识符,即savedUID来记录setuid程序的身份。当一个程序被exec之后,它的有效UID会被拷贝到savedUID中。下表中的第1行表示了一个没有特权的程序,它的真实、有效以及savedUID都是真实用户的数值。第2行正在运行中的setuid程序,它的有效UID被设置成了具有相应特权的UID,而这个特权UID也会被拷贝到saved UID中。

Actions affecting the real, effective, and saved UIDs.
_________________________________________________________________
Action         Real   Effective    Saved

1.exec-normal    R     R          R
2.exec-setuid    R     S          S
3.seteuid(R)     R     R          S
4.seteuid(S)     R     S          S
5.seteuid(R)     R     R          S
6.exec-normal    R     R          R

Key:R-real user identifier; S-special-privilege useridentifier
_________________________________________________________________


seteuid系统调用只会修改有效UID,而不会影响真实的或savedUID。seteuid系统调用被允许将有效UID修改为真实的或savedUID的数值。表中的第3行和第4行表示了一个setuid程序在一直保持正确的真实UID的同时是如何放弃和重新取回它的特殊权限的。第5行和第6行表示了一个setuid程序可以运行一个子进程而不赋予它特殊权限。首先,它会把它的有效UID设置成真实UID。然后,当exec那个子进程的时候,有效UID就会被拷贝到saved UID中,从此就会失去对特权UID的所有访问。

与此类似,也有一个savedGID机制,允许进程在真实GID和最初的有效GID之间进行切换。

再看一个例子:
一个进程的 real user ID 是指运行此进程的用户角色的 ID。
一个进程的 effective user ID 是指此进程目前实际有效的用户ID(也就是权限的大小),effective user ID主要用来校验权限时使用,比如打开文件、创建文件、修改文件、kill别的进程,等等。
如果一个进程是以 root 身份来运行的,那么上面这两个 ID 可以用setuid/seteuid 随便修改,想怎么改就怎么改,改来改去都可以。
但是如果一个进程是以普通用户身份来运行的,那么上面这两个 ID一般来说是相同的,并且也不能随便修改。只有一种情况例外:此进程的可执行文件的权限标记中,设置了“设置用户ID”位!
在命令行中,设置一个可执行文件的“设置用户ID”位的最简单的方法,就是用
[code]chmod +s /path/to/file[/code]
这个命令。
一旦用了这个命令之后,再执行这个文件,
那么生成的进程的 effective user ID 就变成了这个可执行文件的 owneruser ID(属主用户 ID),
而 real user ID 仍然是启动这个程序时所用的用户的 ID。
打个比方来说,如果有这样的一个文件:
[quote]-rw[color=red]s[/color]r-[color=red]s[/color]r-x 1 susesusesusesuse 7902 2006-08-31 13:22 tuid[/quote]
注意这个文件已经用 chmod +s 命令设置过“设置用户 ID”位了。
然后我用 flw 这个用户来执行它,那么生成的进程它的 real user ID 就是flw(因为我是用 flw 运行的),但是 effective user ID 就变成了susesuse(因为这个可执行文件被设置了“设置用户 ID”位,并且它的owner user ID 是 susesuse)。
这时,这个进程实际上就有两个用户权限了。只不过目前生效的是susesuse,因此它目前能够且只能够操作 susesuse用户的文件,如果现在我又想要操作 flw 用户的文件怎么办?
很简单,只需要 seteuid( getuid() )就可以了。执行完这句之后,effective user ID 就变成和 real user ID一样了,都变成 flw 了。

可是如果过了一会儿我又想要变回来怎么办?因为 effective user ID 和real user ID 此时都变成了 flw了,所以操作系统必须得有一个地方保存住原来的“设置用户ID”(也就是可执行文件的 owner user ID),不然等你再想要 seteuid的时候,操作系统就不知道你有没有那个权利了。(总不能再去访问一次文件系统吧?那样也太没有效率了)

操作系统为了能够在设置了 seteuid之后,再次设置回来,所以特地将原来的“设置用户ID”保存下来了,这个保存下来的设置用户 ID自然就叫做“保存的设置用户 ID”。

下面看一段我写的例子程序:
[code]flw@Sleeper:~$ whoami
flw
flw@Sleeper:~$ cat tuid.c
# include
# include
# include

int main( void )
{
    struct passwd *pwd;

    pwd = getpwuid( geteuid());
    printf( "effective UID:[%s]/", pwd->pw_name );

    system( "touch /tmp/foo.txt;ls -l /tmp/foo.txt; rm -rf /tmp/foo.txt" );

    printf( "/set EUID to`flw’../" );
    pwd = getpwnam( "flw");
   seteuid(pwd->pw_uid);

    pwd = getpwuid( geteuid());
    printf( "effective UID:[%s]/", pwd->pw_name );

    system( "touch /tmp/foo.txt;ls -l /tmp/foo.txt; rm -rf /tmp/foo.txt" );

    printf( "/set EUID to`root’../" );
    seteuid(0);

    pwd = getpwuid( geteuid());
    printf( "effective UID:[%s]/", pwd->pw_name );

    system( "touch /tmp/foo.txt;ls -l /tmp/foo.txt; rm -rf /tmp/foo.txt" );

    return 0;
}
flw@Sleeper:~$ su root -c "cc -o tuid tuid.c; chmod +s tuid; ls -altuid"
Password:
-rwsr-sr-x 1 root root 7902 2006-08-31 13:55 tuid
flw@Sleeper:~$ ./tuid
effective UID: [root]
-rw-r--r-- 1 root root 0 2006-08-31 13:55 /tmp/foo.txt

set EUID to `flw’..
effective UID: [flw]
-rw-r--r-- 1 flw root 0 2006-08-31 13:55 /tmp/foo.txt

set EUID to `root’..
effective UID: [root]
-rw-r--r-- 1 root root 0 2006-08-31 13:55 /tmp/foo.txt
flw@Sleeper:~$[/code]
    注意:在这里不能用system函数进行文件的创建,因为system函数在不同的版本中其用户是不确定的,生成的新的文件与该可执行文件的有效用户不一致。因此这里最好使用函数fopen等函数来创建一个新的文件。

 

原创粉丝点击