实际用户ID和实际组ID

实际用户ID和实际组ID确定了进程所属的用户和组。

  • 登录shell会从/etc/passwd文件中读取相应用户密码记录的第三字段和第四字段,置为其实际用户ID和实际组ID
  • 当创建新进程时,将从其父进程继承这些ID

有效用户ID和有效组ID

在大多数UNIX实现中,当进程尝试执行各种操作时,将结合有效用户ID、有效组ID,连同辅助组ID一起来确定授予进程的权限。
通常,有效用户ID及组ID与其相应的实际ID相等,但有两种方法能够致使二者不同:

  1. 使用后面介绍的修改进程凭证的系统调用
  2. 执行set-user-IDset-group-ID程序

Set-User-IDSet-Group-ID程序

set-user-ID程序会将进程的有效用户ID置为可执行文件的用户ID(属主),从而获得常规情况下并不具有的权限。set-group-ID程序对进程有效组ID实现类似任务。

  • 【程序执行步骤1】若可执行文件的set-user-IDset-group-ID)权限位已开启,则将进程的有效用户(组)ID置为可执行文件的属主。若未设置set-user-IDset-group-ID)权限位,则进程的有效用户(组)ID将保持不变。

保存Set-User-ID和保存Set-Group-ID

设计保存set-user-IDsaved set-user-ID)和保存set-group-IDsaved set-group-ID),意在与保存set-user-ID和保存set-group-ID程序结合使用。

  • 【程序执行步骤2】保存set-user-ID和保存set-group-ID的值由对应的有效ID复制而来。无论正在执行的文件是否设置了set-user-IDset-group-ID权限位,这一复制都将进行。

即该ID在程序开始执行时就会把有效ID保存一份副本,而有效ID除了在程序开始执行时可能改变,程序运行过程中也可改变

有不少系统调用,允许将set-user-ID程序的有效用户ID在实际用户ID和保存set-user-ID之间切换。如此一来,程序就能够随时收放自如。

文件系统用户ID和组ID

在Linux系统中,要进行诸如打开文件、改变文件属主、修改文件权限之类的文件系统操作时,决定其操作系统的是文件系统用户ID和组ID(结合辅助组ID),而非有效用户ID和组ID。

  • 通常,文件系统用户ID和组ID等同于相应的有效用户ID和组ID,文件系统ID随相应的有效ID改变。只有当使用Linux特有的两个系统调用(setfsuid()setfsgid())时,才可以刻意制造出文件系统ID和相应有效ID的不同。
  • Linux提供文件系统ID,是历史原因造成的,由于文件系统ID实属异类,且一般都等同于相应的有效ID,所以大部分场景有效ID就可以胜任。

辅助组ID

辅助组ID用于标识进程所属的若干附加的组。

  • 新进程从其父进程处继承这些ID
  • 登录shell从系统组文件中获取其辅助的组ID

获取和修改进程凭证

获取和修改实际、有效和保存设置标识

  1. 获取实际和有效ID
    系统调用getuid()getgid()分别返回调用进程的实际用户ID和组ID;而系统调用geteuid()getegid()则对进程的有效ID实现类似功能。

    #include <unistd.h>
    
    uid_t getuid(void);
                           Returns real user ID of calling process
    uid_t geteuid(void);
                           Returns effective user ID of calling process
    gid_t getgid(void);
                           Returns real group ID of calling process
    gid_t getegid(void);
                           Returns effective froup ID of calling process
  2. 修改有效ID
    setuid()系统调用以给定的uid参数值来修改调用进程的有效用户ID,也可能修改实际用户ID和保存set-user-ID;系统调用setgid()则对相应组实现了类似功能。

    #include <unistd.h>
    
    int setuid(uid_t uid);
    int setgid(gid_t gid);
                           Both return 0 on success, or -1 on error

    适用于setuid()系统调用的规则如下:

    1. 当非特权进程调用setuid()时,仅能修改进程的有效用户ID。而且仅能将有效用户ID修改成相应的实际用户ID或保存set-user-IDsetgid()类似。
    2. 当特权进程以一个非0参数调用setuid()时,其实际用户ID、有效用户ID和保存set-user-ID均被置为uid参数所指定的值。此操作是单向的。setgid()类似,但不会引起特权的丢失,即操作是双向的。

    进程能够使用seteuid()来修改有效用户ID(改为参数 euid 所指定的值),还能使用setegid()来修改其有效组ID(改为参数 egid 所指定的值)。

    #include <unistd.h>
    
    int seteuid(uid_t, euid);
    int setegid(gid_t, egid);
                        Both return 0 on success, or -1 on error

    进程使用seteuid()setegid()来修改其有效ID时,会遵循以下规则:

    1. 非特权级进程仅能将其有效ID修改为相应的实际ID或者保存设置ID。
    2. 特权级进程能够将其有效ID修改为任意值。(若失去特权,可根据规则1来恢复特权)

    对于需要对特权收放自如的set-user-IDset-group-ID程序,更推荐使用seteuid()

  3. 修改实际ID和有效ID
    setreuid()系统调用允许调用进程独立修改其实际和有效用户ID。setregid()系统调用对实际和有效组ID实现了类似功能。

    #include <unistd.h>
    
    int setreuid(uid_t ruid, uid_t euid);
    int setregid(gid_t rgid, gid_t egid);
                           Both return 0 on success, or -1 on error
    • 参数

      • ruid:实际用户ID
      • euid:有效用户ID
      • rgid:实际组ID
      • egid:有效组ID

      参数设为-1可忽略设置指定ID

    • 返回值

      • 成功:返回0
      • 失败:返回-1

    同上,这些函数也有一定的规则,下面以setreuid()说明:

    1. 非特权进程只能将其实际用户ID设置为当前实际用户ID值(即保持不变)或有效用户ID值,且只能将有效用户ID设置为当前实际用户ID、有效用户ID(即无变化)或保存set-user-ID
    2. 特权级进程能够设置其实际用户ID和有效用户ID为任意值
    3. 不管进程拥有特权与否,只要如下条件之一成立,就能将保存set-user-ID设置成(新的)有效用户ID:

      1. ruid不为-1
      2. 对有效用户ID所设置的值不同于系统调用之前的实际用户ID
  4. 获取实际、有效和保存设置ID
    在大多数UNIX实现中,进程不能直接获取(或修改)其保存set-user-ID和保存set-group-ID的值。Linux提供了两个(非标准)系统调用来实现此功能:getresuid()getresgid()

    #define _GNU_SOURCE
    #include <unistd.h>
    
    int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
    int getresgid(uid_t *rgid, uid_t *egid, uid_t *sgid);
                                   Both return 0 on success, or -1 on error

    getresuid()系统调用将调用进程的当前实际用户ID、有效用户ID和保存set-user-ID返回至给定的3个参数所指定的位置。getresgid()系统调用针对相应的组ID实现了类似功能。

  5. 修改实际、有效和保存设置ID
    setresuid()系统调用允许调用进程独立修改其3个用户ID值。每个用户ID的新值由系统调用的3个参数给定。setresgid()系统调用对相应的组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);
                           Both return 0 on success, or -1 on error
    • 参数

      • ruid:实际用户ID
      • euid:有效用户ID
      • rgid:实际组ID
      • egid:有效组ID
      • suid:保存set-user-ID
      • sgid:保存set-group-ID

      参数设为-1可忽略设置指定ID

    • 返回值

      • 成功:返回0
      • 失败:返回-1

    同上,这些函数也有一定的规则,下面以setresuid()说明:

    1. 非特权进程能够将实际用户ID、有效用户ID和保存set-user-ID中的任一ID设置为实际用户ID、有效用户ID或保存set-user-ID之中的任意值
    2. 特权级进程能够对其实际用户ID、有效用户ID和保存set-user-ID做任意设置
    3. 不管系统调用是否对其他ID做了任何改动,总是将文件系统用户ID设置为与有效用户ID(可能是新值)相同

    本章修改多个ID的系统调用都遵循:要么全部修改成功,要么全部失败

获取和修改文件系统ID

前述所有修改进程有效用户ID或组ID的系统调用总是会修改相应的文件系统ID。要想独立于有效ID而修改文件系统ID,必须使用Linux特有的系统调用:setfsuid()setfsgid()

#include <sys/fsuid.h>

int setfsuid(uid_t fsuid);
                        Always returns the previous file-system user ID
int setfsgid(gid_t fsgid);
                        Always returns the previous file-system group ID

setfsuid()系统调用将进程文件系统用户ID修改为参数fsuid所指定的值;setfsgid()系统调用将进程文件系统组ID修改为参数fsgid所指定的值。
同样,此类变更也存在一些规则,下面以setfsuid()为例:

  1. 非特权进程能够将文件系统用户ID设置为实际用户ID、有效用户ID、文件系统用户ID(即保持不变)或保存set-user-ID的当前值
  2. 特权级进程能够将文件系统用户ID设置为任意值

获取和修改辅助组ID

getgroups系统调用会将当前进程所属组的集合返回至由参数grouplist指向的数组中

#include <unistd.h>

int getgroups(int gidsetsize, gid_t grouplist[]);
    Returns number of group IDs placed in grouplist on success, or -1 on error
  • 参数

    • gidsetsize:在该参数中指定其长度
    • grouplist:指定存储集合的内存地址,调用进程必须负责为grouplist数组分配存储空间
  • 返回值

    • 成功:返回置于grouplist中的组ID数量
    • 失败:返回-1

特权级进程能够使用setgroups()initgroups()来修改其辅助组ID集合

#define _BSD_SOURCE
#include <grp.h>

int setgroups(size_t gidsetsize, const gid_t *grouplist);
int initgroups(const char *user, gid_t group);
                                    Both return 0 on success, or -1 on error
  • setgroups()系统调用用grouplist数组所指定的集合来替换调用进程的辅助组ID。参数gidsetsize指定了置于参数grouplist数组中的组ID数量。
  • initgroups()函数将扫描/etc/groups文件,为user创建属组列表。以此来初始化调用进程的辅助组ID。另外,也会将参数group指定的组ID追加到进程辅助组ID的集合中。

总结

本章介绍了进程凭证的概念,通俗一点说就是进程的各种权限,需要理解各ID之间的区别和关系,然后介绍了获取和修改进程凭证的系统调用。

  • 本章中提到的set-user-IDset-group-ID程序,在shell中经常这么设置:

    # chmod u+s filename    // set-user-ID,即Set UID, SUID
    # chmod g+s filename    // set-group-ID,即Set GID, SGID
    # chmod o+t dirname     // Sticky Bit,粘滞位,即SBIT;该权限只对目录有效,作用为该目录下的文件用户只能删除自己的而不能删除其他人的;本章没有涉及这个权限,只是顺带引出

    该类程序都有执行权限,带有此特征的常见命令有:passwd(1)mount(8)umount(8)su(1)等等

  • 使 有效用户ID 在 实际用户ID 和 保存set-user-ID 之间来回切换的例子:

    euid = geteuid();    //Save initial effective user ID (which is same as saved set-user-ID)
    
    if (seteuid(getuid()) == -1)    //Drop privileges
        errExit("seteuid");
    if (seteuid(euid) == -1)        //Regain privileges
        errExit("seteuid");

标签: Linux, C/C++, TLPI

添加新评论