更改用户 ID 和更改组 ID

    UNIX 系统中的特权(如能改变当前日期的表示法)和访问控制(如能否读、写一个特定文件)都是基于用户 ID 和组 ID 的。当程序需要增加或降低特权,需要允许或阻止对某些资源的访问时,都需要更换自己的用户 ID 或组 ID。
    可以使用 setuid 函数设置实际用户 ID 和有效用户 ID,使用 setgid 函数设置实际组 ID 和有效组 ID。
#include <unistd.h>
int setuid(uid_t uid);
int setgid(gid_t gid);
                        /* 两个函数返回值:若成功,返回 0;否则,返回 -1 */

    更改用户 ID 有若干规则(这些规则也适用于组 ID)。
    1、若进程具有超级用户特权,则 setuid 会将实际用户 ID、有效用户 ID 及保存的设置用户 ID(saved set-user-ID)设置为 uid。
    2、若进程没有超级用户特权,但是 uid 等于实际用户 ID 或保存的设置用户 ID,则 setuid 只将有效用户 ID 设置为 uid。
    3、如果上面两个条件都不满足,则将 errno 设置为 EPERM,并返回 -1。
    这里假设 _POSIX_SAVED_IDS 为真,如果没有提供这种功能,则上面所说的关于保存的设置用户 ID 部分都无效(在 POSIX.1 2001 版中,保存的 ID 是强制性功能。而在早期版本中则是可选的。为了测试某实现是否支持该功能,应用程序可在编译时测试常量 _POSIX_SAVED_IDS,或者在运行时以 _SC_SAVED_IDS 参数调用 sysconf 函数)。
    关于内核所维护的这 3 个用户 ID,还要注意以下几点。
    1、只有超级用户进程可以更改实际用户 ID。通常,实际用户 ID 是在登录时由 login(1) 程序设置的,而且决不会改变它。因为 login 是一个超级用户进程,所以当它调用 setuid 时,会设置所有 3 个用户 ID。
    2、仅当对程序文件设置了设置用户 ID 位时,exec 函数才改变有效用户 ID。任何时候都可以调用 setuid 将有效用户 ID 设置为实际用户 ID 或保存的设置用户 ID。
    3、保存的设置用户 ID 位是由 exec 复制有效用户 ID 而得到的。如果设置了文件的设置用户 ID 位,则在 exec 根据文件的用户 ID 设置了进程的有效用户 ID 以后,这个副本就被保存起来了。
    下表总结了更改这 3 个用户 ID 的不同方法。

    注意,在 进程标识符操作函数中所述的 getuid 和 geteuid 函数只能获得实际用户 ID 和有效用户 ID 的当前值,并没有可移植的方法去获得保存的设置用户 ID 的当前值(FreeBSD 8.0 和 Linux 3.2.0 提供了 getresuid 和 getresgid 函数,它们可以分别用于获取保存的设置用户 ID 和保存的设置组 ID)。
    历史上,BSD 支持 setreuid 函数,可用于交换实际用户 ID 和有效用户 ID 的值。
#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);
                              /* 两个函数返回值:若成功,返回 0;否则,返回 -1 */

    如若其中任一参数的值为 -1,则表示相应的 ID 保持不变。
    一个非特权用户总能交换实际用户 ID 和有效用户 ID。这就允许一个设置用户 ID 程序交换成用户的普通权限,以后又可再次交换回设置用户 ID 权限。POSIX.1 引进了保存的设置用户 ID 特性后,其规则也相应加强,它允许一个非特权用户将其有效用户 ID 设置为保存的设置用户 ID(4.3 BSD 没有保存的设置用户 ID 特性,而是使用上面这两个函数来代替。这就允许一个非特权用户交换这两个用户 ID 的值。但当使用此特性的程序生成 shell 进程时,它应该在子进程调用 exec 前将子进程的实际用户 ID 和有效用户 ID 都设置成普通用户 ID。否则当实际用户 ID 具有特权时,shell 进程就可调用 setreuid 交换两个用户 ID 值以获得更多权限)。
    POSIX.1 包含了 seteuid 和 setegid 函数,它们类似于 setuid 和 setgid,但只更改有效用户 ID 和有效组 ID。
#include <unistd.h>
int seteuid(uid_t uid);
int setegid(gid_t gid);
                              /* 两个函数返回值:若成功,返回 0;否则,返回 -1 */

    一个非特权用户可将其有效用户 ID 设置为其实际用户 ID 或其保存的设置用户 ID,而一个特权用户则可将有效用户 ID 设置为 uid。
    下图描述了本文所述的更改 3 个不同用户 ID 的各个函数。

    为说明保存的设置用户 ID 特性的用法,下面就来观察一个使用了该特性的 at(1)程序,它用于调度将来某个时刻要运行的命令(Linux 3.2.0 上的 at 程序的设置用户 ID 是 daemon 用户,FreeBSD 8.0、Mac OS X 10.6.8 以及 Solaris 10 上的 at 程序的设置用户 ID 是 root 用户。这允许 at 命令对守护进程拥有的特权文件具有写权限,守护进程代表用户运行 at 命令。在 Linux 3.2.0 上,程序是用 atd(8)守护进程运行的,在 FreeBSD 8.0 和 Solaris 10 上是通过 cron 守护进程运行的,在 Max OS X 10.6.8 上是通过 launchd(8)守护进程运行的)。为了防止被欺骗而运行不被允许的命令或读、写没有访问权限的文件,at 程序和最终代表用户运行命令的守护进程必须在用户特权和守护进程特权之间切换。下面列出了工作步骤。
    1、程序文件是由 root 用户拥有的,并且其设置用户 ID 位已设置,则运行此程序时得到下列结果:
        实际用户 ID = 我们的用户 ID(未改变)
        有效用户 ID = root
        保存的设置用户 ID = root
    2、at 程序做的第一件事就是降低特权,以用户特权运行。它调用 setuid 函数把有效用户 ID 设置为实际用户 ID,此时得到:
        实际用户 ID = 我们的用户 ID(未改变)
        有效用户 ID = 我们的用户 ID
        保存的设置用户 ID = root(未改变)
    3、at 程序以我们的用户特权运行,直到它需要访问控制哪些命令即将运行,这些命令需要何时运行的配置文件时,at程序的特权会改变。这些文件由为用户运行命令的守护进程持有。at命令调用 setuid 函数把有效用户 ID 设置为 root,因为 setuid 的参数等于保存的设置用户 ID,所以这种调用是许可的,这就是为什么需要保存的设置用户 ID 的原因。现在得到:
        实际用户 ID = 我们的用户 ID(未改变)
        有效用户 ID = root
        保存的设置用户 ID = root(未改变)
    这里因为有效用户 ID 是 root,所以文件访问是允许的。
    4、修改文件从而记录了将要运行的命令以及它们的运行时间以后,at 命令通过调用 seteuid,把有效用户 ID 设置为用户 ID,降低它的特权,防止对特权的误用。此时得到:
        实际用户 ID = 我们的用户 ID(未改变)
        有效用户 ID = 我们的用户 ID
        保存的设置用户 ID = root(未改变)
    5、守护进程开始用 root 特权运行,代表用户运行命令。守护进程调用 fork,子进程调用 setuid 将它的用户 ID 更改为我们的用户 ID。因为子进程以 root 特权运行,更改了所有的 ID,所以:
        实际用户 ID = 我们的用户 ID
        有效用户 ID = 我们的用户 ID
        保存的设置用户 ID = 我们的用户 ID
    现在守护进程可以安全地代表我们运行命令,因为它只能访问我们通常可以访问的文件,并没有额外的权限。

    以这种方式使用保存的设置用户 ID,只有在需要提升特权的时候,我们通过设置程序文件的设置用户 ID 而得到的额外权限。然而,其他时间进程在运行时只具有普通权限。如果进程不能在其结束部分切换回保存的设置用户 ID,那么就不得不在全部运行时间都保持额外的权限(这可能会造成麻烦)。
    注意,上面针对用户 ID 所说的规则也适用于各个组 ID,不过附属组 ID 不受 setgid、setregid 和 setegid 函数的影响。

猜你喜欢

转载自aisxyz.iteye.com/blog/2392696
id