很好的setuid资料整理----转载

来源:互联网 发布:淘宝和易趣的共同点 编辑:程序博客网 时间:2024/05/02 04:29

转自:http://os.chinaunix.net/a2007/0429/960/000000960387.shtml 

 

近期准备把我能找到的关于setuid的相关资料整理一下,包括书籍、论文以及FreeBSD的源代码等。

    就从Bach的书开始吧。。。

    Maurice J.Bach 的《The Design of The UNIX Operating System》一书中对这个问题的论述。。。

    p227

    7.6 THE USER ID OF A PROCESS

    内核会给每个进程关联两个和进程ID无关的用户ID,一个是真实用户ID,还有一个是有效用户ID或者称为setuid(set user ID)。真实用户ID用于标识由谁为正在运行的进程负责。有效用户ID用于为新创建的文件分配所有权、检查文件访问许可,还用于通过kill系统调用向其它进程发送信号时的许可检查。内核允许一个进程以调用exec一个setuid程序或者显式执行setuid系统调用的方式改变它的有效用户ID。

    所谓setuid程序是指一个设置了许可模式字段中的setuid bit的可执行文件。当一个进程exec一个setuid程序的时候,内核会把进程表以及u区中的有效用户ID设置成该文件所有者的ID。为了区分这两个字段,我们把进程表中的那个字段称作保存用户ID。可以通过一个例子来演示这两个字段的区别。

    setuid系统调用的语法是 setuid(uid) ,其中,uid是新的用户ID,该系统调用的结果取决于有效用户ID的当前值。如果调用进程的有效用户ID是超级用户,内核会把进程表以及u区中的真实和有效用户ID都设置成uid。如果调用进程的有效用户ID不是超级用户,仅当uid等于真实用户ID或保存用户ID时,内核才会把u区中的有效用户ID设置成uid。否则,该系统调用将返回错误。一般来说,一个进程会在fork系统调用期间从父进程那儿继承它的真实和有效用户ID,这些数值即使经过exec系统调用也会保持不变。

    存储在u区中的有效用户ID是最近一次setuid系统调用或是exec一个setuid程序的结果;只有它会被用于文件访问许可。进程表中的保存用户ID使得一个进程可以通过执行setuid系统调用把有效用户ID设置成它的值,以此来恢复最初的有效用户ID。

    setuid程序的例子:login,mkdir。

    Uresh Vahalia 的《UNIX Internals:The New Frontiers》一书中对这个问题的论述。。。

    p27

    2.3.3 User Credentials

    UID和GID这样的标识符会影响文件的所有权和访问许可,以及向其它进程发送信号的能力。这些属性统称为凭证。

    每个进程都有两对ID ——真实的和有效的。当一个用户登录的时候,login程序会把两对ID设置成密码数据库(/etc/passwd文件,或某些如Sun Microsystems的NIS之类的分布式机制)中指定的UID和GID。当一个进程fork的时候,子进程将从父进程那儿继承它的凭证。

    有效UID和有效GID印象文件的创建和访问。在创建文件的时候,内核将文件的所有者属性设置成创建进程的有效UID和有效GID。在访问文件的时候,内核使用进程的有效UID和GID来判断它是否能够访问该文件。真实UID和真实GID标识进程的真实所有者,会影响到发送信号的权限。对于一个没有超级用户权限的进程来说,仅当它的真实或有效UID于另一个进程的真实UID匹配时它才能向那个进程发送信号。

    有三个系统调用可以改变凭证。如果一个进程调用exec执行一个安装为suid模式的程序,内核将把进程的有效UID修改成文件的所有者。同样,如果该程序安装为sgid模式,内核则会去修改调用进程的有效GID。UNIX提供这个特性是想赋予用户特殊的权限以完成一些特定的任务。

    一个用户还可以通过调用setuid或setgid来改变它的凭证。超级用户可以通过这些系统调用改变真实的和有效的UID以及GID。普通用户则只能通过这些调用来把它们的有效UID或GID改回到真实的数值。

    System V和BSD UNIX在处理凭证方面存在着一些差别。SVR3还维护了一个saved UID和saved GID,分别是在调用exec之前的有效UID和GID的数值。setuid和setgid系统调用还可以把有效ID恢复为保存的数值。4.3BSD不支持这一特性,它允许一个用户属于一个辅组(supplemental group)的集合(使用setgroups系统调用)。用户创建的文件将属于它的主组(primary group),而用户则既可以访问属于主组的文件,也可以访问属于辅组的文件。

    SVR4整合了上述所有特性。它支持附组,也会在exec的时候维护saved UID和GID。

    setuid程序的例子:passwd。

    Marshall Kirk McKusick, George V. Neville-Neil 的《The Design and Implementation of the FreeBSD Operating System》一书中对这个问题的论述。。。

    3.7 User, Group, and Other Identifiers

    每个FreeBSD进程的状态里都有一个UID和一组GID。一个进程的文件系统访问特权就是由它的UID和GIDs来定义的。通常,这些标识符都是新进程创建的时候从父进程那儿自动继承过来的。只有超级用户才能修改一个进程的真实UID或真实GID。这个方案在各种特权之间进行了严格的区分,确保除了超级用户之外的其它任何用户都无法获得特权。

    每个文件都有三组许可bit,分别用于所有者、组以及其它用户的读、写或执行许可。这些许可bit将按如下顺序进行检查:

     1、如果文件的UID和进程的UID相同,则仅应用所有者的许可,不再检查组和其它用户的许可。

     2、如果UID不匹配,但文件的GID和进程的众多GID之一匹配,则仅引用组的许可,不再检查所有者和其它用户的许可。

     3、仅当进程UID和GID与文件的UID和GID都不匹配时,才会去检查其它用户的许可。如果这些许可不允许所请求的操作,该操作就会失败。

    一个进程的UID和GIDs是从它的父进程那儿继承来的。当一个用户登录的时候,login程序会在执行exec系统调用运行用户的登录shell之前设置好UID和GIDs,因此,后续的所有进程都会继承到恰当的标识符。

    我们经常会想赋予一个用户有限的额外特权。......为了解决这个问题,内核允许程序在运行过程中创建被赋予特权的程序。以不同的UID运行的程序被称为setuid程序,以一个额外的组特权运行的程序被称为setgid程序。当运行一个setuid程序的时候,进程的许可将被扩展以包括与程序相关联的UID的许可。该程序的UID就被称为进程的有效UID,而进程最初的UID则被称为真实UID。同样,执行一个setgid程序会把进程的许可扩展为程序的GID的许可,相应的也有有效GID和真实GID的定义。

    系统可以通过setuid和setgid程序来提供对文件或服务的受控访问。当然,这样的程序必须仔细编写,以保证它们只具有一些有限的功能。

    UID和GIDs是作为每个进程的状态的一部分来维护的。由于历史原因,GIDs被实现成了一个显著的GID(即有效GID)和一个GIDs的辅助数组,不过在逻辑上则被看作是一组GIDs。在FreeBSD中,那个显著的GID就是GIDs数组中的第一个条目。辅助数组的大小是固定的(FreeBSD中是16),不过可以通过重新编译内核来修改这个数值。

    FreeBSD是通过把运行setgid程序的进程的辅组数组中的第0个元素设置成文件的属组来实现setgid功能的。之后就可以像普通进程那样对许可进行检查了。由于存在额外的组,setgid程序就能够比一个运行没有特殊权限的程序的用户进程访问更多的文件。为了避免在运行一个setgid程序的时候丢失与第0个数组元素中的组相关联的特权,login程序会在初始化用户的辅组数组的时候将第0个数组元素复制到第一个数组元素中。因此,当运行的setgid程序修改第0个元素的时候,用户不会丢失任何特权,因为曾经保存在第0个数组元素中的组仍然可以从第一个数组元素中得到。

    setuid功能是通过把进程的有效UID从用户的数值修改为被运行的程序的数值来实现的。和setgid一样,保护机制此时将毫不变样地允许访问,同时也不会意识到程序正在运行setuid。由于一个进程在同一时刻只能有一个UID,在运行setuid的时候就可能会丢失某些特权。在加载新的有效UID的时候,之前的真实UID将会继续作为真实UID。不过真实UID是不会用于任何确认检查的。

    一个setuid进程在运行过程中可能会想临时取消它的特殊权限。比如,它可能只在运行开始和结束的时候需要访问某个受限文件的特殊权限。在其余的运行时间中,它应当只具有真实用户的权限。在BSD的早期版本中,特权的回收是通过对真实的和有效的UID进行切换来完成的。由于只有有效UID被用于访问控制,这个方法既提供了所需的语义,又提供了一个隐藏特殊权限的地方。这个方法的缺点是很容易就混淆了真实的和有效的UID。

    在FreeBSD中,使用了一个额外的标识符,即saved UID来记录setuid程序的身份。当一个程序被exec之后,它的有效UID会被拷贝到saved UID中。下表中的第1行表示了一个没有特权的程序,它的真实、有效以及saved UID都是真实用户的数值。第2行正在运行中的setuid程序,它的有效UID被设置成了具有相应特权的UID,而这个特权UID也会被拷贝到saved UID中。

    Actions affecting the real, effective, and saved UIDs.

    _________________________________________________________________

    Action Real Effective Saved

    1.exec-normal R R R

    2.exec-setuid R S S

    3.seteuid(R) R R S

    4.seteuid(S) R S S

    5.seteuid(R) R R S

    6.exec-normal R R R

    Key:R-real user identifier; S-special-privilege user identifier

    _________________________________________________________________

    seteuid系统调用只会修改有效UID,而不会影响真实的或saved UID。seteuid系统调用被允许将有效UID修改为真实的或saved UID的数值。表中的第3行和第4行表示了一个setuid程序在一直保持正确的真实UID的同时是如何放弃和重新取回它的特殊权限的。第5行和第6行表示了一个setuid程序可以运行一个子进程而不赋予它特殊权限。首先,它会把它的有效UID设置成真实UID。然后,当exec那个子进程的时候,有效UID就会被拷贝到saved UID中,从此就会失去对特权UID的所有访问。

    与此类似,也有一个saved GID机制,允许进程在真实GID和最初的有效GID之间进行切换。

    FreeBSD6.0 setuid()函数源代码分析。。。

    int setuid(struct thread *td, struct setuid_args *uap)

     |

     |

     ------------------------------------

     | struct proc *p = td->td_proc; |

     | struct ucred *newcred, *oldcred; |

     | uid_t uid; |

     | struct uidinfo *uip; |

     | int error; |

     ------------------------------------

     |

     |

     -------------------------

     | uid = uap->uid; |

     | newcred = crget(); |

     | uip = uifind(uid); |

     | PROC_LOCK(p); |

     | oldcred = p->p_ucred; |

     -------------------------

     |

     |

     ----------------------------------------------------------

     | (uid != oldcred->cr_ruid && |

     cr_uid && >

     | (error = suser_cred(oldcred, SUSER_ALLOWJAIL)) != 0)? |

     ----------------------------------------------------------

     是否入参uid既不等于真实uid又不等于有效uid,当前进程还没

     有超级用户权限?

     /

     / No Yes

     /

     -----------------------------

     | crcopy(newcred, oldcred); |

     -----------------------------

     用oldcred对newcred进行初始化

     |

     | --------------------

     ----------------------------- | PROC_UNLOCK(p); |

     cr_ruid)? > | uifree(uip); |

     ----------------------------- | crfree(newcred); |

     入参uid不等于真实uid吗? | return(error); |

     / --------------------

     / No Yes |

     / |

     / ------------------------------ |

     / | change_ruid(newcred, uip); | |

     / | setsugid(p); | |

     / ------------------------------ |

     / 将newcred的真实uid设为入参uid, |

     / 设置当前进程的P_SUGID标志 |

     / / |

     / / |

     / / |

    ------------------------------- |

    cr_svuid)? > |

    ------------------------------- |

    入参uid不等于saved uid吗? |

     / |

     / No Yes |

     / |

     / ------------------------------- |

     / | change_svuid(newcred, uid); | |

     / | setsugid(p); | |

     / ------------------------------- |

     / 将newcred的saved uid设为入参uid, |

     / 设置当前进程的P_SUGID标志 |

     / / |

     / / |

     / / |

    ------------------------------ |

    cr_uid)? > |

    ------------------------------ |

    入参uid不等于有效uid吗? |

     | |

     | No Yes |

     | |

     | ------------------------------- |

     | | change_euid(newcred, uid); | |

     | | setsugid(p); | |

     | ------------------------------- |

     | 将newcred的有效uid设为入参uid, |

     | 设置当前进程的P_SUGID标志 |

     | | |

     | | |

     | ------------------------- |

     | | p->p_ucred = newcred; | |

     | | PROC_UNLOCK(p); | |

     ------------>| uifree(uip); | |

     | crfree(oldcred); | |

     | return(0); | /

     ------------------------- /

     将当前进程的u_cred结构体更改 /

     为newcred,使前面的修改生效 /

     /

     /

     /

     -------

     | END |

     -------

    Simson Garfinkel, Alan Schwartz, Gene Spafford 的 《Practical Unix & Internet Security》 一书中对这个问题的论述。。。

    

    16.4 Tips on Writing SUID/SGID Programs

    If you are writing programs that are SUID or SGID, you must take added precautions in your programming. An overwhelming number of Unix security problems have been caused by SUID/SGID programs. Consider the rules described in this section in addition to those in previous sections.

    1.\"Don''t do it. Most of the time, it''s not necessary.\"

     Thanks to Patrick H. Wood and Stephen G. Kochan, Unix System Security (Hayden Books, 1985) for this insightful remark.

    2.Avoid writing SUID shell scripts.

    3.If you are using SUID to access a special set of files, don''t. Instead, create a special group for your files and make the program SGID to that group. If you must use SUID, create a special user for the purpose.

    4.If your program needs to perform some functions as superuser, but generally does not require SUID permissions, consider putting the SUID part in a different program, and constructing a carefully controlled and monitored interface between the two.

    5.If you need SUID or SGID permissions, use them for their intended purpose as early in the program as possible, and then revoke them by returning the effective, and real, UIDs and GIDs to those of the process that invoked the program.

    6.If you have a program that absolutely must run as SUID, try to avoid equipping the program with a general-purpose interface that allows users to specify much in the way of commands or options.

    7.Erase the execution environment, if at all possible, and start fresh. Many security problems have been caused because there was a significant difference between the environment in which the program was run by an attacker and the environment in which the program was developed.

    8.If your program must spawn processes, use only the execve( ), execv( ), or execl( ) calls, and use them with great care. Avoid the execlp( ) and execvp( ) calls because they use the PATH environment variable to find an executable, and you might not run what you think you are running. Avoid system( ) and popen( ) at all costs.

    9.If you must provide a shell escape, be sure to setgid(getgid( )) and setuid(getuid( )) before executing the user''s command and use them in the correct order! You must reset the group ID before you reset the user ID, or the call will fail.

    10.In general, use the setuid( ) and setgid( ) functions and their friends to bracket the sections of your code that require superuser privileges. For example:

     /* setuid program is effectively superuser so it can open the master file */

     fd = open(\"/etc/masterfile\",O_RDONLY);

     assert(seteuid(getuid( )) == 0);

     /* Give up superuser now, but we can get it back.*/

     assert(geteuid() == getuid( ));/* Insure that the euid is what we expect. */

     if(fd

    Not all versions of Unix allow you to switch UIDs in this way; moreover, the semantics of the various versions of setuid( ), seteuid( ), and setreuid( ) have been shown to vary between Unix flavors, and even be misimplemented. It''s also crucial both to check their return values and to separately test to ensure that the UIDs are as you expect them. Read Chen, Wagner, and Dean''s paper \"Setuid Demystified\" (

    http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf

    ) before you even think about writing code that tries to save and restore privileges.

    11.If you must use pipes or subshells, be especially careful with the environment variables PATH and IFS. One approach is to erase these variables and set them to safe values. For example:

     putenv("PATH=/bin:/usr/bin:/usr/ucb");

     putenv("IFS= ");

    Then, examine the environment to be certain that there is only one instance of the variable: the one you set. An attacker can run your code from another program that creates multiple instances of an environment variable. Without an explicit check, you may find the first instance, but not the others; such a situation could result in problems later on. In particular, step through the elements of the environment yourself rather than depending on the library getenv( ) function.

    Another approach, simpler but more drastic, is to create an empty environment and fill it with only those variables that you know are OK. This environment can then be passed to execve( ):

     char *env[MAX_ENV];

     int mysetenv(const char *name, const char *value) {

     static char count = 0;

     char buff[255];

     if (count == MAX_ENV) return 0;

     if (!name || !value) return 0;

     if (snprintf(buff, sizeof(buff), "%s=%s", name, value)

     ...And then in the program...

     if (mysetenv("PATH", "/bin:/usr/bin") &&

     mysetenv("SHELL", "/bin/sh") &&

     mysetenv("TERM", "vt100") &&

     mysetenv("USER", getenv("USER")) &&

     mysetenv("LOGNAME", getenv("LOGNAME")) &&

     mysetenv("HOME", getenv("HOME"))) {

     execve(myprogram,NULL,env);

     perror(myprogram);

     } else {

     perror("Unable to establish safe environment");

     }

    12.Use the full pathname for all files that you open. Do not make any assumptions about the current directory. (You can enforce this requirement by doing a chdir("/tmp/root/") as one of the first steps in your program, but be sure to check the return code!)

    13.Consider statically linking your program. If a user can substitute a different module in a dynamic library, even carefully coded programs are vulnerable. (We have some serious misgivings about the trend in commercial systems towards completely shared, dynamic libraries.

    14.Consider using perl -T or taintperl for your SUID programs and scripts. Perl''s tainting features often make Perl more suited than C to SUID programming. For example, taintperl insists that you set the PATH environment variable to a known "safe value" before calling system( ). The program also requires that you "untaint" any variable that is input from the user before using it (or any variable dependent on that variable) as an argument for opening a file.

    15.However, note that you can still get yourself in a great deal of trouble with taintperl if you circumvent its checks or if you are careless in writing code. Also note that using taintperl introduces dependence on another large body of code working correctly: we suggest you skip using taintperl if you believe that you can code at least as well as Larry Wall.

     Hint: if you think you can, you are probably wrong.

    IEEE Std 1003.1™, 2004 Edition

    Standard for Information Technology —

    Portable Operating System Interface (POSIX)

    System Interfaces 中对setuid()的论述。。。

    NAME

     setuid—set user ID

    SYNOPSIS

     #include

     int setuid(uid_t uid);

    DESCRIPTION

     If the process has appropriate privileges, setuid( ) shall set the real user ID,

     effective user ID, and the saved set-user-ID of the calling process to uid.

     If the process does not have appropriate privileges, but uid is equal to the real user

     ID or the saved set-user-ID, setuid( ) shall set the effective user ID to uid; the real

     user ID and saved set-user-ID shall remain unchanged.

     The setuid( ) function shall not affect the supplementary group list in any way.

    RETURN VALUE

     Upon successful completion, 0 shall be returned. Otherwise, ?1 shall be returned and

     errno set to indicate the error.

    ERRORS

     The setuid( ) function shall fail, return ?1, and set errno to the corresponding value

     if one or more of the following are true:

     [EINVAL] The value of the uid argument is invalid and not supported by the

     implementation.

     [EPERM] The process does not have appropriate privileges and uid does not match the

     real user ID or the saved set-user-ID.

    EXAMPLES

     None.

    APPLICATION USAGE

     None.

    RATIONALE

     The various behaviors of the setuid( ) and setgid( ) functions when called by

     non-privileged processes reflect the behavior of different historical implementations.

     For portability, it is recommended that new non-privileged applications use the

     seteuid( ) and setegid( ) functions instead.

     The saved set-user-ID capability allows a program to regain the effective user ID

     established at the last exec call. Similarly, the saved set-group-ID capability allows

     a program to regain the effective group ID established at the last exec call.

     These capabilities are derived from System V. Without them, a program might have to run

     as superuser in order to perform the same functions, because superuser can write on the

     user’s files. This is a problem because such a program can write on any user’s files,

     and so must be carefully written to emulate the permissions of the calling process

     properly. In System V, these capabilities have traditionally been implemented only via

     the setuid( ) and setgid( ) functions for non-privileged processes. The fact that the

     behavior of those functions was different for privileged processes made them difficult

     to use. The POSIX.1-1990 standard defined the setuid( ) function to behave differently

     for privileged and unprivileged users. When the caller had the appropriate privilege,

     the function set the calling process’ real user ID, effective user ID, and saved

     set-user ID on implementations that supported it. When the caller did not have the

     appropriate privilege, the function set only the effective user ID, subject to

     permission checks. The former use is generally needed for utilities like login and su,

     which are not conforming applications and thus outside the scope of IEEE Std

     1003.1-2001. These utilities wish to change the user ID irrevocably to a new value,

     generally that of an unprivileged user. The latter use is needed for conforming

     applications that are installed with the set-user-ID bit and need to perform operations

     using the real user ID.

     IEEE Std 1003.1-2001 augments the latter functionality with a mandatory feature named

     _POSIX_SAVED_IDS. This feature permits a set-user-ID application to switch its

     effective user ID back and forth between the values of its exec-time real user ID and

     effective user ID. Unfortunately, the POSIX.1-1990 standard did not permit a conforming

     application using this feature to work properly when it happened to be executed with

     the (implementation-defined) appropriate privilege. Furthermore, the application did

     not even have a means to tell whether it had this privilege. Since the saved

     set-user-ID feature is quite desirable for applications, as evidenced by the fact that

     NIST required it in FIPS 151-2, it has been mandated by IEEE Std 1003.1-2001. However,

     there are implementors who have been reluctant to support it given the limitation

     described above.

     The 4.3BSD system handles the problem by supporting separate functions: setuid( )

     (which always sets both the real and effective user IDs, like setuid( ) in IEEE Std

     1003.1-2001 for privileged users), and seteuid( ) (which always sets just the effective

     user ID, like setuid( ) in IEEE Std 1003.1-2001 for non-privileged users). This

     separation of functionality into distinct functions seems desirable. 4.3BSD does not

     support the saved set-user-ID feature. It supports similar functionality of switching

     the effective user ID back and forth via setreuid( ), which permits reversing the real

     and effective user IDs. This model seems less desirable than the saved set-user-ID

     because the real user ID changes as a side effect. The current 4.4BSD includes saved

     effective IDs and uses them for seteuid( ) and setegid( ) as described above. The

     setreuid( ) and setregid( ) functions will be deprecated or removed.

     The solution here is:

     . Require that all implementations support the functionality of the saved

     set-user-ID, which is set by the exec functions and by privileged calls to

     setuid( ).

     . Add the seteuid( ) and setegid( ) functions as portable alternatives to setuid( )

     and setgid( ) for non-privileged and privileged processes.

     Historical systems have provided two mechanisms for a set-user-ID process to change its

     effective user ID to be the same as its real user ID in such a way that it could return

     to the original effective user ID: the use of the setuid( ) function in the presence of

     a saved set-user-ID, or the use of the BSD setreuid( ) function, which was able to swap

     the real and effective user IDs. The changes included in IEEE Std 1003.1-2001 provide a

     new mechanism using seteuid( ) in conjunction with a saved set-user-ID. Thus, all

     implementations with the new seteuid( ) mechanism will have a saved set-user-ID for

     each process, and most of the behavior controlled by _POSIX_SAVED_IDS has been changed

     to agree with the case where the option was defined. The kill ( ) function is an

     exception. Im

0 0