作者:程姚根,华清远见嵌入式学院讲师。
一、进程与权限
A.进程是Linux/Unix操作系统中最重要的抽象之一。
B.进程是一个处于执行期的程序(目标代码存储在某种介质上)
A process is a program(object code stored on some media) in the midst of execution.
而进程在执行过程中经常涉及到文件访问操作等。
1.一个运行中的进程究竟可以访问哪些资源,而不能访问哪些资源?
2.一个进程运行过程中如何实现在某个执行阶段拥有一些权限而在另一个阶段又具备另外一种权限呢?
上述状况对应于进程的访问权限以及进程执行中不同权限的切换。这一切都和进程实际用户ID,进程有效用户ID,进程保存设置用户ID有关联。
二、进程的用户ID
1.实际用户ID(real user id,RUID)
为该进程的创建者的用户ID,也可以说是进程的执行者。该ID仅root用户可以修改
2.有效用户ID(effective user id,EUID)
该ID用户标识用户进程执行操作的权限。例如:如果EUID是0即(root),此时进程拥有root用户权限。普通用户可以将EUID设置为RUID或者SUID,而超级用户可以将EUID设置为任意的合法UID。
注意:对于一个可执行的二进制文件,如果其set-user-id bit被设置,则运行该程序时,对应进程的有效用户ID(EUID)为该文件的文件所有者用户ID。
3.保存设置用户ID(saved set-user-id,SUID)
对于没有设置set-uid-bit的可执行程序而言,其对应进程的保存设置用户ID(SUID)为其实际用户ID;而对于设置了set-uid-bit的程序而言,其对应的保存设置用户ID(SUID)为该可执行文件的文件拥有者用户ID。只能root用户权限,才能更改(当一个程序文件运行的时候,其值已经确定)。
注意:linux不提供返回保存的设置-用户-ID的函数
总结:
RUID代表此进程是哪个用户创建的, EUID代表此进程所拥有的权限,SUID保存了EUID,当EUID改变后,如果想回到以前的EUID,此时SUID将发挥作用。
三、相关API
#include
#include
int setuid(uid);
可以用setuid函数设置实际用户ID和有效用户ID。注意,我们并不能想怎么设就怎么设。有若干规则需要我们遵守:
(1)若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID,以及保存的设置-用户-D设置为uid。
(2)若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置-用户-ID,则setuid只将有效用户ID设置为uid。不改变实际用户ID和保存的设置-用户-ID。
(3)如果上面两个条件都不满足,则errno设置为EPERM,并返回出错.。
关于内核所维护的三个用户ID,还要注意下列几点
(1)只有超级用户进程可以更改实际用户ID。通常,实际用户ID是在用户登录时,由login(1)程序设置的,而且决不会改变它。因为login是一个超级用户进程,当它调用setuid时,设置所有三个用户ID。
(2)仅当对程序文件设置了 set-user-id bit时,exec函数设置有效用户ID为文件所有者。如果set-user-id bit没有设置,则exec函数不会改变有效用户ID,而将其位置原先值。任何时候都可以调用setuid,将有效用户ID设置为实际用户ID或保存的设置-用户-ID。自然,不能讲有效用户ID设置为任意随机值。
(3)保存的设置-用户-ID是由exec从有效用户ID复制的。在exec按文件用户ID设置了有效用户ID后,即进行这种复制,并将其副本保存起来。
四、探究
案例一:
有个要在MacOS上实现一个关机程序,它熟悉linux,其实老祖宗都是unix,所以MacOS对他并不陌生。如下他写了如下程序实现关机
execlp("shutdown","shutdown","-h","1",NULL);
编译运行:
从上面看,这段代码可以实现关机,但是有个不符合要求的地方,运行程序不能是超级用户,只能是普通用户。
他很苦恼,后来找到了我,我做了如下事情
呵呵,可以了。我将这个可执行文件的owner和group都改为了root,然后有讲其set-user-id bit位加以了设置。这样以普通用户运行这个程序时,此进程的EUID就是root了,这样满足了shutdown命令执行的权限。
案例二:
#include
#include
#include
#include
#include
#include
void test_read_file(const char *name)
{
int fd = -1;
fd = open(name ,O_RDWR);
if(fd < 0){
printf("=[ERROR]:read failed.\n");
}else{
printf("=[OK]:read successful\n");
close(fd);
}
}
//打印uid和euid
void p_states()
{
int uid = 0;
int euid = 0;
printf("------Current states--------\n");
printf("real uid\t %d\n",getuid());
printf("effective uid\t %d\n",geteuid());
printf("----------------------------\n");
}
//调用setuid
void run_setuid_fun(int uid)
{
if(setuid(uid) == -1)
{
printf("=[ERROR]:setuid(%d) error\n",uid);
}
p_states();
}
//调用setuid
void run_seteuid_fun(int uid)
{
if(setuid(uid) == -1)
{
printf("=[ERROR]:seteuid(%d) error\n",uid);
}
p_states();
}
int main()
{
int t_re = 0;
const char *file = "root_only.txt";
printf("\nTEST 1:\n");
p_states();
//此时real uid = login user id
//effective uid = root
//saved uid = root
test_read_file(file);
getchar();
printf("\nTEST 2:setuid(getuid())\n");
run_seteuid_fun(getuid());
//此时real uid = login user id
//effective uid = login user id
//saved uid = root
test_read_file(file);
getchar();
printf("\nTEST 3:setuid(0)\n");
run_setuid_fun(0);
//此时real uid = login user id
//effective uid = root
//saved uid = root
test_read_file(file);
getchar();
printf("\nTEST 4:setuid(0)\n");
run_setuid_fun(0);
//此时real uid = root
//effective uid = root
//saved uid = root
test_read_file(file);
getchar();
printf("\nTEST 5:setuid(503)\n");
run_setuid_fun(503);
//此时real uid = login user id
//effective id = login user id
//saved uid = login user id
test_read_file(file);
getchar();
printf("\nTEST 6:setuid(0)\n");
//read uid = login user id
//effective uid = login user id
//saved uid = login user id
run_setuid_fun(0);
test_read_file(file);
return 0;
}
编译运行:
root_only.txt文件建立:
第一次:
RUID:实际用户
EUID:文件所有者(root)
SUID:文件所有者(root)
此时进程拥有root用户权限,能对root_only.txt进行读写操作
第二次:
此时普通用户调用setuid(getuid()),只会将EUID改为getuid(),其他都不变
RUID:实际用户
EUID:实际用户
SUID:root
此时进程没有有root用户权限,不能能对root_only.txt进行读写操作
第三次:
此时普通用户调用seteuid(0),只会将EUID改为0,其他都不变
RUID:实际用户
EUID:root
SUID:root
此时进程拥有root用户权限,能对root_only.txt进行读写操作
第四次:
此时进程拥有root用户权限,调用setuid(0),会将三个ID都设置为0
RUID:root
EUID:root
SUID:root
此时进程拥有root用户权限,能对root_only.txt进行读写操作
第五次:
此时进程拥有root用户权限,调用setuid(503),会将三个ID都设置为503
RUID:503
EUID:503
SUID:503
此时进程拥有普通用户权限,不能对root_only.txt进行读写操作
第六次:
此时进程拥有普通用户权限,调用setuid(0),此时RUID,SUID都不为0,这一次操作将失败
RUID:503
EUID:503
SUID:503
此时进程拥有普通用户权限,不能能对root_only.txt进行读写操作