linux进程凭证(权限)

2018-05-21 13:39:42

每个进程都有一套用数字表示的用户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进程凭证做了简单的介绍,如果有疑问可以给我留言。


备注

1.编译器版本gcc4.8,运行环境centos7 64位
2.原文地址http://www.freecls.com/a/2712/2e

©著作权归作者所有
收藏
推荐阅读
  • linux用户和组

    linux上每个用户都拥有一个唯一的用户名和一个相对应的用户id(uid),用户可以隶属于一个或多个组。每个组也拥有一个唯一的组名和组id(gid)。用户和组主要是用来控制资源访问权限的。记录用户相关...

  • err
    linux动态内存分配

    进程可以通过增加堆的大小来分配内存,堆就是一段长度可变的连续的虚拟内存,开始于未初始化数据段末尾,随着内存的分配和释放增减。通常堆的当前内存边界称为program bre...

  • err
    linux进程

    进程是一个可执行程序的实例。程序包含了一系列信息,这些信息描述了如何在运行时创建一个进程,所包含的内容如下。1.二进制格式标识,linux上用的是elf格式2.机器语言指...

  • err
    linux文件io(open、read、write、close)

    所有执行io操作的系统调用都是以文件描述符(大于0的整数)来指代打开的文件。文件描述符可以表示诸如管道(pipe)、fifo、socket、终端、设备和普通文件。对于每个...

  • err
    linux系统调用及错误处理

    系统调用是内核提供给外部程序的接口,进程可以通过系统调用来一自己的名义来执行某些动作。在深入了解系统调用之前,先关注以下几点。1.系统调用处理器会从用户态切换到核心态以便...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。