分类:
2008-11-26 18:01:56
8.11 changing user ids and group ids
本文侧重讲述了real user id, effective user id的使用接口,更改以及注意事项。并提及了saved set-user-id
#include int setuid(uid_t uid); int setgid(gid_t gid); |
Both return: 0 if OK, 1 on error |
首先,一个进程有real user id, effective use id, saved set-user-id.
Saved set-user-id是一个进程被exec启动时,从其effective user id拷贝过来的。目的是为了对effective user id做一个备份。因为你可以在以后修改effective user id,那么日后还可以利用save set-user-id将你的原始的effective user id改回来。
而一个进程可以通过函数得到的就只有real user id和effective user id。
规则:
1. 只有super user进程可以更改他的real user id.
2. 任何user的进程都可以更改它的effective user id。方法:(1)如果是通过调用exec运行程序,且程序被设置了set-user-id标记,那么新程序的effective user id就是可执行程序文件的owner user id。(2)使用setuid(uid),如果一个进程是super user进程,那么通过setuid调用,不但修改了effective user id,连同real user id和save set-user-id都被修改成了uid. 来设置其effective user id。如果进程是一个普通用户进程,那么调用setuid(uid)仅仅修改他的effective user id为uid,不过前提是uid等于本进程的real user id或者saved set-user-id。可见effective user id也不是随便让你舍得,设置成real user id是使你获得了你本来就具有的权限,一般来说权限是降低了。将effective user id设置成saved set-user-id其实是使你仍然获得了你当初调用exec使获得的effective user id的权限。(因为saved set-user-id本来就是在执行exec时从effective user id拷贝过来的)。
下面是apue2原文:
There are rules for who can change the IDs. Let's consider only the user ID for now. (Everything we describe for the user ID also applies to the group ID.)
1.If the process has superuser privileges, the setuid function sets the real user ID, effective user ID, and saved set-user-ID to uid.
2.If the process does not have superuser privileges, but uid equals either the real user ID or the saved set-user-ID, setuid sets only the effective user ID to uid. The real user ID and the saved set-user-ID are not changed.
3.If neither of these two conditions is true, errno is set to EPERM, and 1 is returned
We can make a few statements about the three user IDs that the kernel maintains.
1.Only a superuser process can change the real user ID. Normally, the real user ID is set by the login(1) program when we log in and never changes. Because login is a superuser process, it sets all three user IDs when it calls setuid.
2.The effective user ID is set by the exec functions only if the set-user-ID bit is set for the program file. If the set-user-ID bit is not set, the exec functions leave the effective user ID as its current value. We can call setuid at any time to set the effective user ID to either the real user ID or the saved set-user-ID. Naturally, we can't set the effective user ID to any random value.
3.The saved set-user-ID is copied from the effective user ID by exec. If the file's set-user-ID bit is set, this copy is saved after exec stores the effective user ID from the file's user ID.
下表其实是对上述原则的一个综述:
Figure 8.18. Ways to change the three user IDs |
|||||
ID |
exec |
setuid(uid) |
|
||
set-user-ID bit off |
set-user-ID bit on |
superuser |
unprivileged user |
|
|
real user ID |
unchanged |
unchanged |
set to uid |
unchanged |
|
effective user ID |
unchanged |
set from user ID of program file |
set to uid |
set to uid (uid必须==real uid或者saved set user id) |
|
saved set-user ID |
copied from effective user ID |
copied from effective user ID |
set to uid |
unchanged |
|
还举了一个例子,man程序,他有一个专门的用户假设是man与其对应. Man 用户可以访问的一些文件和路径。Man程序设置了set-user-id。
1.我们执行man程序,当我们exec man程序后,我们:
real user ID = our user ID
effective user ID = man
saved set-user-ID = man
2.Man程序以effective user id = man访问了他的配置文件如/etc/man.config和一些manual资源。
3. 在man为我们用户执行任何命令之前,他调用了setuid(getuid()),即将自己的effective user id设置成了real user id,此时:
real user ID = our user ID (unchanged)
effective user ID = our user ID
saved set-user-ID = man (unchanged)
4. 一些工作完成之后,man调用了setuid(euid),这里的euid是对应的man用户的id。Euid == saved set-user-id。所以这个设置可以成功。此时:
real user ID = our user ID (unchanged)
effective user ID = man
saved set-user-ID = man (unchanged)
5. The man program
can now operate on its files, as its effective user ID is man
还有2个函数,可以同时设置real uid和effective user id,而且可允许普通user 更改其real user id。可以允许将real user id设置成effective user id,当然也允许将effective user id设置成real user id。这函数就是:
#include int
setreuid(uid_t ruid, uid_t euid); int
setregid(gid_t rgid, gid_t egid); |
Both return: 0 if OK, 1 on error |
问题:你竟然将real use id设置成了effective user id,那么如果user调用exec执行一个设置了set-user-id的且属于root的程序,那么此时real user id为user, 而effective user id是root, 调用setreuid()后,可以将real user id和effective user id换一下,此时进程的real user id成了root。 那么如果此时spawn出一个子进程,且这个子进程是我们自己写的,那么子进程 的real user id都会继承为root,那么此时,我们就可以再次将real user id和effective user id互换以使我们的程序获取root的effective user 权限,或者根本不必替换,我们的real user id 为root就已经是我们具有了很高的权限了。这就是不安全的了。为了避免这样的问题,apue中有如下解释:
Be aware, however, that when programs that used this feature spawned a shell, they had to set the real user ID to the normal user ID before the exec. If they didn't do this, the real user ID could be privileged (from the swap done by setreuid) and the shell process could call setreuid to swap the two and assume the permissions of the more privileged user. As a defensive programming measure to solve this problem, programs set both the real user ID and the effective user ID to the normal user ID before the call to exec in the child
即,在你通过调用setreuid提升了real user id的权限后,一旦要spawn出一个子进程,那么在你调用exec之前,这个子进程的real user id甚至effective user id就都应该被设置成普通用户的id。而不是权限提升之后的用户的id. 但是我做的测试结果表明,系统不会自动这么做:spawn出来的进程在执行了exec后,得到新进程的real user id依然是权限提升之后的用户id .
如下为我的测试,及其结果:
程序一: 调用总程序,他的owner是root,被设置了set-user-id,他会展示setreuid的效果,并在切换了real user id 和effective user id之后fork出一个子进程然后exec一个新程序,并检查新程序的real user id和effective user id。
#include
#include
#include
#include
#include
int main()
{
uid_t uid = getuid();
uid_t eid = geteuid();
printf( "uid = %d, euid = %d\n", uid, eid );
if( setreuid( eid, uid )<0 )
printf("error occurs\n");
else
printf("success\n");
uid = getuid();
eid = geteuid();
printf( "uid = %d, euid = %d\n", uid, eid );
fflush(stdout);
pid_t pid = fork();
if( pid<0 )
puts( "fork error occurs\n" );
else if( pid == 0 )
{
//child
puts("this is child");
uid_t uid = getuid();
uid_t eid = geteuid();
printf( "child uid = %d, euid = %d\n", uid, eid );
fflush(stdout);
execl( "./tmpexe", (char*)0 );
exit(0);
}
puts("this is father");
waitpid(pid,0,0);
fflush(stdout);
return 0;
}
程序二: 被exec的程序
#include
#include
#include
int main()
{
uid_t uid = getuid();
uid_t eid = geteuid();
printf(" executed app uid=%d, eid=%d\n" , uid, eid );
return 0;
}
下面是命令流程:
shaoting@desktopbj-LabSD:/home/shaoting/mytest> g++ tmpexec.cpp -o tmpexe
shaoting@desktopbj-LabSD:/home/shaoting/mytest> g++ setreuid.cpp
shaoting@desktopbj-LabSD:/home/shaoting/mytest> su
Password:
desktopbj-LabSD:/home/shaoting/mytest # chown root a.out
desktopbj-LabSD:/home/shaoting/mytest # chmod u+s a.out
desktopbj-LabSD:/home/shaoting/mytest # exit
exit
shaoting@desktopbj-LabSD:/home/shaoting/mytest> ./a.out
uid = 1003, euid = 0
success
uid = 0, euid = 1003
this is child
child uid = 0, euid = 1003
this is father
executed app uid=0, eid=1003
shaoting@desktopbj-LabSD:/home/shaoting/mytest>
可见,子进程里exec后,其权限依然是real user id 为root。
Linux的man 2 setreuid里,有一句话:
Linux: Unprivileged users may only set the real user ID to the real user ID or the effective user ID.
Linux: If the real user ID is set or the effective user ID is set to a value not equal to the previous real user ID, the saved set-user-ID will be set to the new effective user ID.
可见,real user id就算设置也顶多设置成与effective user id相同。
Effective user id如果被设置成不等于real user id的另外的值且也不等于effective user id的话,saved set-user-id随之也会被更改。