(1)进程中三种用户ID的含义
表12-2列出了进程执行时,与进程相关联的三种用户ID,这三种ID在Linux书刊中经常提及,但也是易混淆不好理解的地方。
表12-2 三种用户ID意义表
与每个进程相关联的用户ID和组ID
实际用户ID | 我们实际上是谁,ID号保存的是启动进程用户ID和组ID |
实际组ID | |
有效用户ID | 用于文件存取许可权检查。当执行码设置了设置-用-ID (set-user-ID)位时,此时进程的有效用户为该文件所属用户,同时就获取了所属用户的用户权限。有效组同理 |
有效组ID | |
保存的设置-用户-ID | 用来保存有效用户ID和有效组ID的副本 |
保存的设置-组-ID |
实际用户ID和实际组ID标识我们究竟是谁,这两个字段是用户登录时取自口令文件中的登录项。通常,在一个登录会话期间这些值并不改变,但是超级用户进程可以对这两个ID值随便改。
有效用户ID、有效组ID决定了文件访问权限。有效用户ID、有效组ID主要用来校验文件权限时使用,比如打开文件、创建文件、修改文件、kill别的进程等。
保存的设置-用户-ID和设置-组-ID在执行一个程序时保存了有效用户ID和有效组ID的副本。
通常,当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常是实际组ID。
每个文件有一个所有者(属主)和组所有者(属组),所有者由stat结构(文件属性结构)中的st_uid表示,组所有者则由st_gid成员表示。当在文件方式字 (st_mode)中设置一个特殊标志时(其含义是“当执行此文件时,将进程的有效用户ID设置为文件的所有者 (st_uid)”),此时有效用户ID就不一定等于实际用户ID。与此相类似,在文件方式字中可以设置另一位,它使得执行此文件进程的有效组ID设置为文件的组所有者 (st_gid)。在文件方式字中的这两位被称之为设置-用户-ID(set-user-ID)位和设置-组-ID(set-group-ID)位。注意设置-用户-ID位和保存的设置-用户-ID是两个不同的字段,前一个是一个特殊标志,后一个是用来保存有效用户ID的副本。
例如假设Y用户有一执行码test,此时test的设置-用户-ID(set-user-ID)位没有设置。X用户执行Y用户的执行码test时,此时test实际用户ID等于X用户ID,有效用户ID等于X用户ID,保存的设置用户ID等于X用户ID,三类组ID同理。由于,有效用户ID决定了执行码的用户权限,所以test执行码是X用户权限,有权限读取和修改X用户的文件,而对Y用户的文件则不一定有权限。
当test的设置-用户-ID(set-user-ID)位进行了设置时,X用户执行Y用户的执行码test,此时test实际用户ID等于X用户ID,有效用户ID等于Y用户ID,保存的设置-用户-ID等于Y用户ID,三类组ID同理。此时test执行码为Y用户权限,有权限读取和修改Y用户的文件,而对X用户的文件不一定有权限。如果要让test某一段时间内有X用户的权限,则可用seteuid(getuid())方法把有效用户ID修改为实际用户ID(即X用户ID);随后test又想恢复Y用户权限,则可把有效用户ID重置,因为设置-用户-ID是以前有效用户ID的副本,在非特权用户下系统会根据设置-用户-ID的值判断此有效用户ID设置是否允许。在非特权用户下,有效用户ID重新设置时只能等于实际用户ID或保存的设置-用户-ID。
(2)setuid函数和setgid函数原型
setuid(设置真实的用户识别码)
所需头文件 | #include <unistd.h> | |
函数说明 | setuid()用来重新设置执行目前进程的用户识别码。不过,要让此函数有作用,其有效的用户识别码必须为0(root)。在Linux下,当root使用setuid()来变换成其他用户识别码时,root权限会被抛弃,完全转换成该用户身份,也就是说,该进程往后将不再具有可setuid()的权利,如果只是向暂时抛弃root 权限,稍后想重新取回权限,则必须使用seteuid() | |
函数原型 | int setuid(uid_t uid) | |
函数传入值 | uid | 设置实际用户ID号 |
函数返回值 | 成功 | 0 |
出错 | -1,失败原因存于errno中 | |
附加说明 | 一般在编写具有setuid root的程序时,为减少此类程序带来的系统安全风险,在使用完root权限后建议马上执行setuid(getuid());来抛弃root权限 |
setgid(设置真实的组识别码)
所需头文件 | #include <unistd.h> | |
函数说明 | setgid()用来将目前进程的真实组识别码(real gid)设成参数gid值。如果是以超级用户身份执行此调用,则real、effective与saved gid都会设成参数gid | |
函数原型 | int setgid(gid_t gid) | |
函数传入值 | gid | 设置实际组ID号 |
函数返回值 | 成功 | 0 |
出错 | -1,失败原因存于errno中 | |
错误代码 | EPERM:并非以超级用户身份调用,而且参数gid 并非进程的effective gid或saved gid值之一 |
(3)setuid和setgid函数使用说明
可以用setuid函数设置实际用户ID和有效用户ID。与此类似,可以用setgid函数设置实际组ID和有效组ID。
使用setuid改变用户ID的规则:
① 若进程具有超级用户特权,则setuid(uid)执行时将实际用户ID、有效用户 ID,以及保存的设置-用户-ID设置为uid。
② 若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置-用户-ID,则setuid(uid)执行时只将有效用户ID设置为uid,不改变实际用户ID和保存的设置-用户-ID。
③ 如果上面两个条件都不满足,则errno设置为EPERM,并返回出错。
关于内核所维护的三个用户ID的说明:
① 只有超级用户进程可以更改实际用户ID。通常,实际用户ID是在用户登录时,由login程序设置的,而且决不会改变它。因为login是一个超级用户进程,当它调用setuid时,设置所有三个用户ID。
② 仅当对程序文件设置了设置-用户-ID位时,exec函数会设置有效用户ID。如果设置-用户-ID位没有设置,则exec函数不会改变有效用户ID,而将其维持为原先值。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置-用户-ID。
③ “保存的设置-用户-ID是进程exec时从有效用户ID复制而来,复制后并将此副本保存起来。
表12-3列出了改变三种用户ID的不同方法表。从表中可以看出,有效用户ID是主角,因为有效用户ID决定了进程对文件的访问权限,非特权用户下有效用户ID的值重新设置时只能等于实际用户ID或保存的设置-用户-ID。
表12-3 改变三种用户ID的不同方法表
ID | exec | setuid(uid) | ||
设置-用户-ID位关闭 | 设置-用户-ID位打开 | 超级用户 | 非特权用户 | |
实际用户ID | 不变 | 不变 | 设为uid | 不变 |
有效用户ID | 不变 | 设置为程序文件的用户ID | 设为uid | 设为uid |
保存的设置-用户-ID | 从有效用户ID复制 | 从有效用户ID复制 | 设为uid | 不变 |
(4)seteuid和setegid函数原型
seteuid(设置有效的用户识别码)
所需头文件 | #include <unistd.h> | |
函数说明 | seteuid()用来重新设置执行目前进程的有效用户识别码。在Linux下,seteuid(euid)相当于setreuid(-1,euid) | |
函数原型 | int seteuid(uid_t euid) | |
函数传入值 | euid | 设置有效用户的ID号 |
函数返回值 | 成功 | 0 |
出错 | -1,失败原因存于errno中 |
setegid(设置有效的组识别码)
所需头文件 | #include <unistd.h> | |
函数说明 | 设置执行目前进程的有效组识别号 | |
函数原型 | int setegid(gid_t egid) | |
函数传入值 | egid | 设置有效组ID号 |
函数返回值 | 成功 | 0 |
出错 | -1,失败原因存于errno中 |
(5)seteuid和setegid函数说明
seteuid和setegid它们只更改有效用户ID和有效组ID。
执行seteuid(uid)时,对于一个非特权用户只有uid等于实际用户ID或其保存的设置-用户-ID时才能设置。对于一个超级特权用户则可将有效用户ID可设置为uid(这区别于setuid函数,特权用户下它更改的是三个用户ID)。
(6)函数举例说明几种ID的作用
在X用户下编写setid.c源文件,内容如下。
#include <unistd.h>
#include <pwd.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc,char **argv)
{
pid_t my_pid,parent_pid;
uid_t my_uid,my_euid;
gid_t my_gid,my_egid;
my_uid=getuid();
my_euid=geteuid();
my_gid=getgid();
my_egid=getegid();
printf("User ID%ld\n",my_uid);
printf("Effective User ID%ld\n",my_euid);
printf("Group ID%ld\n",my_gid);
printf("Effective Group ID%ld\n",my_egid) ;
system("wc -l xx") ;
seteuid(getuid()) ;
setegid(getgid()) ;
my_uid=getuid();
my_euid=geteuid();
my_gid=getgid();
my_egid=getegid();
printf("after seteuid(getuid())\n");
printf("User ID%ld\n",my_uid);
printf("Effective User ID%ld\n",my_euid);
printf("Group ID%ld\n",my_gid);
printf("Effective Group ID%ld\n",my_egid) ;
system("wc -l xx") ;
return 0 ;
}
① 编译 gcc setid.c –o setid。
② 设置执行码设置-用户-ID(set-user-ID)位和设置-组-ID(set-group-ID)位。
$chmod u+s setid
$chmod g+s setid
③ 用vim xx文件输入几行内容,用chmod 700设置xx文件的权限。
④ 用id命令查看X用户的用户ID和组ID,结果如下:
uid=1008(X) gid=1003(XX)
⑤ 到Y用户下,用id命令查看Y用户的用户ID和组ID,结果如下:
uid=1016(Y) gid=1001(YY)
⑥ 在Y用户到相应目录执行./setid,执行结果如下:
User ID1016
Effective User ID1008
Group ID1001
Effective Group ID1003
3 xx
after seteuid(getuid())
User ID1016
Effective User ID1016
Group ID1001
Effective Group ID1001
wc: xx: Permission denied
摘录自《深入浅出Linux工具与编程》