原文地址http://daileinote.com/computer/linux_sys/07
每个进程都有一套用数字表示的用户id和组id,这些id决定了进程执行时具体的权限。
实际用户id和实际组id
这2个id决定了进程所属的用户和组。假设我们用root用户登录,那么我们在shell中创建的新进程都会从父进程shell中继承这些id。
有效用户id和有效组id
进程将结合有效用户id、有效组id、辅助组id来决定进程的权限。比如访问一些资源,能否给另一个进程发送信号。有效用户id为0的特权进程拥有所有权限。有些系统调用只能由特权进程执行。
通常,有效用户id及有效组id与实际用户id,实际组id相等,有两种方法可以改变。
1.下面讲到的系统调用
2.执行set-user-ID和set-group-ID的程序
设置用户id(set-user-ID)和设置组id(set-group-ID)程序
可执行文件的用户id和组id决定了该文件的所有权。另外,可执行文件还拥有两个特别的权限位,set-user-id位和set-group-id位。可使用chmod命令来设置这些权限位。非特权用户可以对自己拥有的文件进行设置,特权用户能够对任何文件设置。
[root@izj6cfw9yi1iqoik31tqbgz c]# ll a.out
-rwxr-xr-x 1 root root 13336 May 21 11:32 a.out
[root@izj6cfw9yi1iqoik31tqbgz c]# chmod u+s a.out
[root@izj6cfw9yi1iqoik31tqbgz c]# chmod g+s a.out
[root@izj6cfw9yi1iqoik31tqbgz c]# ll a.out
-rwsr-sr-x 1 root root 13336 May 21 11:32 a.out
从上例可以看出,当设置了set-user-id和set-group-id权限位时,那么可执行位x标识被替换成s。
当运行set-user-id程序时,内核会将进程的有效用户id设置为该文件的用户id。set-group-id程序一样。通过这种方法可以修改进程的有效用户id或者有效组id。
我们知道/etc/shadow文件记录了密码信息,只有root用户才能访问,但是普通用户却可以用passwd命令来修改自己的密码,要使修改后的密码存入/etc/shadow文件,那么普通用户必须要能对文件/etc/shadow有操作权限,所以可以得出,passwd命令肯定设置了set-user-id位。
保存设置用户id和保存设置组id
当用户执行程序时,将会发生如下事件
1.若可执行文件设置了设置用户id(设置组id),则进程的有效用户(组)id置为可执行文件的拥有者,若未设置,则保持不变。
2.此时,保存设置用户id,和保存设置组id值由对应的有效用户(组)id复制过来,不管有没设置set-user-id或set-group-id权限位。
文件系统用户id和组id
在linux系统中,要进行诸如打开文件,改变文件属主,修改文件权限之类的操作,决定的是文件系统用户id和组id(结合辅助组id),而非有效用户id和组id。
通常,只要有效用户或有效组id发生了变化,则相应的文件系统id或组id也将随之改变为同一值。之所以存在这两个id,是有历史因素在。目前,从严格意义来讲,这2种id保留已无必要。所以不再累述。
辅助组id
辅助组id用户标识进程所属的若干附加的组。将这些id和有效id相结合就能决定进程的权限。
获取和修改进程凭证
#include <unistd.h>
uid_t getuid(void); //获取实际用户id
uid_t geteuid(void); //获取有效用户id
gid_t getgid(void); //获取实际组id
gid_t getegid(void); //获取有效组id
int setuid(uid_t uid); //设置实际用户id,有效用户id,保存用户id
int setgid(gid_t gid); //设置实际组id,实际有效组id,实际保存组id
1.当非特权进程调用setuid()时,只能修改进程的有效用户id,而且只能将有效用户id改成相应的实际用户id。
2.特权用户调用setuid()设置uid为非0时,会同时设置实际用户id,有效用户id,和保存设置用户id,且为单向的,因为设置了之后进程就失去了特权。
setgid原理跟setuid一样,只是它在特权用户下可以随便设置。
#include <unistd.h>
int seteuid(uid_t euid); //设置有效用户id
int setegid(gid_t egid); //设置有效组id
//返回-1代表失败
特权用户可以将其有效id修改为任意值,但是修改为非0后将失去特权,但是可以恢复
下面是普通用户运行set-user-id程序的例子
euid = geteuid(); //保存有效用户id
seteuid(getuid()); //失去特权
seteuid(euid); //恢复特权
修改实际id和有效id
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
//返回-1代表失败
linux独有的获取实际、有效和保存设置id
#define _GNU_SOURCE
#include <unistd.h>
int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);
//返回-1代表失败,成功返回0
linux独有的设置实际、有效和保存设置id
#define _GNU_SOURCE
#include <unistd.h>
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);
//返回-1代表失败,成功返回0
linux特有的修改文件系统id
#include <sys/fsuid.h>
int setfsuid(uid_t fsuid);
int setfsgid(gid_t fsgid);
//返回之前的文件系统id
获取和修改辅助组id
#include <unistd.h>
int getgroups(int gidsetsize, gid_t grouplist[]);
//返回grouplist里的元素个数,错误返回-1
//如果gidsetsize=0则返回进程属组个数
#include <limits.h>
gid_t grouplist[NGROUPS_MAX + 1]
//考虑到移植性,数组中可能包含有效组id,所以要+1
grouplist内存自己手动分配,gidsetsize代表组id个数。
修改辅助组id
#define _BSD_SOURCE
#include <grp.h>
int setgroups(size_t gidsetsize, const gid_t *grouplist);
int initgroups(const char *user, gid_t group);
//返回0代表成功,返回-1代表失败
setgroups()系统调用用grouplist里集合替换进程的辅助组id,gidsetsize代表个数
initgroups()函数将扫描/etc/group文件为user创建属组列表,也会把group追加到进程辅助组id,主要是用户创建登录会话程序-login(1);
例子
以下为nobody用户调用root用户的程序a.out(此程序为设置用户id程序),nobody用户id为99
ll a.out
-rwsr-xr-x 1 root root 8728 May 21 17:39 a.out
#define _BSD_SOURCE /* Get getpass() declaration from <unistd.h> */
#define _XOPEN_SOURCE /* Get crypt() declaration from <unistd.h> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define _GNU_SOURCE
#include <unistd.h>
#include <limits.h>
int main(int argc, char *argv[]){
uid_t ruid;
uid_t euid;
uid_t suid;
getresuid(&ruid, &euid, &suid);
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
//失去特权
seteuid(2);
getresuid(&ruid, &euid, &suid);
printf("特权调用 seteuid(2);\n");
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
setuid(4);
getresuid(&ruid, &euid, &suid);
printf("非特权调用 setuid(4);\n");
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
setuid(99);
getresuid(&ruid, &euid, &suid);
printf("非特权调用 setuid(99);\n");
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
//恢复特权
seteuid(0);
//setuid(0);
getresuid(&ruid, &euid, &suid);
printf("非特权调用 seteuid(0);---恢复特权\n");
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
setuid(0);
getresuid(&ruid, &euid, &suid);
printf("特权调用 setuid(0);\n");
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
//不可逆失去特权
setuid(2);
getresuid(&ruid, &euid, &suid);
printf("特权调用 setuid(2);---永久失去特权\n");
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
setuid(0);
getresuid(&ruid, &euid, &suid);
printf("非特权调用 setuid(0);\n");
printf("real id:%d effective id:%d saved id:%d\n\n", ruid, euid, suid);
}
/*
real id:99 effective id:0 saved id:0
特权调用 seteuid(2);
real id:99 effective id:2 saved id:0
非特权调用 setuid(4);
real id:99 effective id:2 saved id:0
非特权调用 setuid(99);
real id:99 effective id:99 saved id:0
非特权调用 seteuid(0);---恢复特权
real id:99 effective id:0 saved id:0
特权调用 setuid(0);
real id:0 effective id:0 saved id:0
特权调用 setuid(2);---永久失去特权
real id:2 effective id:2 saved id:2
非特权调用 setuid(0);
real id:2 effective id:2 saved id:2
*/
总结
本文对linux进程凭证做了简单的介绍,如果有疑问可以给我留言。