news 2026/4/21 7:28:21

TLPI 第9章 读书笔记:Process Credentials

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TLPI 第9章 读书笔记:Process Credentials

笔记和练习博客总目录见:开始读TLPI。

每个进程都有一组关联的数字用户标识符(UID)和组标识符(GID)。有时,这些被称为进程凭证。这些标识符如下:

  • 实际用户ID和组ID;
  • 有效用户ID和组ID;
  • 保存的设置用户ID和保存的设置组ID;
  • 文件系统用户ID和组ID(特定于Linux);以及
  • 补充组ID。

在本章中,我们将详细探讨这些进程标识符的目的,并描述可用于检索和更改它们的系统调用和库函数。我们还讨论了特权进程和非特权进程的概念,以及 set-user-ID 和 set-group-ID 机制的使用,这些机制允许创建以指定用户或组的权限运行的程序。

9.1 Real User ID and Real Group ID

真实用户ID和组ID用于标识进程所属的用户和组。作为登录过程的一部分,登录 shell 从 /etc/passwd 文件(第 8.1 节)中用户密码记录的第三和第四字段获取其真实用户和组 ID。当创建新进程(例如,当 shell 执行程序时)时,它会从父进程继承这些标识符。

9.2 Effective user ID and Effective Group ID

在大多数 UNIX 实现中(如第 9.5 节所述,Linux 略有不同),有效用户 ID 和组 ID 与附加组 ID 共同用于确定进程在尝试执行各种操作时(即系统调用)所被授予的权限。例如,这些标识符决定了进程在访问诸如文件和 System V 进程间通信(IPC)对象等资源时所获得的权限,这些资源本身也有与之关联的用户和组 ID,以确定它们属于谁。正如我们将在第 20.5 节中看到的,有效用户 ID 也被内核用来确定一个进程是否可以向另一个进程发送信号。

有效用户 ID 为 0(即 root 用户 ID)的进程拥有超级用户的所有特权。这样的进程称为特权进程。某些系统调用只能由特权进程执行。

💡 特权进程的概念很重要,除root用户进程外,通过setuid升权的进程也属于特权进程。或者简单说,特权进程就是EUID=0。

在第39章中,我们描述了Linux对能力的实现,这是一种将授予超级用户的权限划分为多个独立单元的方案,可以独立启用和禁用这些单元。

通常,有效用户和组ID的值与相应的真实ID相同,但有效ID可以在两种情况下取得不同的值。一种方式是通过第9.7节中讨论的系统调用。另一种方式是通过执行设置用户ID(set-user-ID)和设置组ID(set-group-ID)程序。

💡 真实ID是其物理身份,有效ID是其可扮演的角色,当然,大部分时间都是在做自己。

9.3 Set-User-lD and Set-Group-lD Programs

一个 set-user-ID 程序允许一个进程获得它通常不会拥有的权限,通过将进程的有效用户 ID 设置为与可执行文件的用户 ID(所有者)相同的值。一个 set-group-ID 程序对进程的有效组 ID 执行类似的任务。(术语 set-user-ID 程序和 set-group-ID 程序有时缩写为 set-UID 程序和 set-GID 程序。)

像其他任何文件一样,可执行程序文件也有一个关联的用户 ID 和组 ID,用于定义文件的所有权。此外,可执行文件有两个特殊的权限位:set-user-ID 位和 set-group-ID 位。(实际上,每个文件都有这两个权限位,但我们关心的是它们在可执行文件中的使用。)这些权限位是通过 chmod 命令设置的。一个无特权用户可以为他们拥有的文件设置这些位。有特权的用户(CAP_FOWNER)可以为任何文件设置这些位。下面是一个例子:

# ls -l a.out -rwxr-xr-x. 1 vagrant vagrant 17288 Mar 3 02:16 a.out # chmod u+s a.out # chmod g+s a.out # ls -l a.out -rwsr-sr-x. 1 vagrant vagrant 17288 Mar 3 02:16 a.out

如本例所示,一个程序可以同时设置这两个位,尽管这种情况不常见。当使用 ls –l 列出具有 set-user-ID 或 set-group-ID 权限位设置的程序的权限时,通常用于表示执行权限已设置的 x 会被 s 替换。

当运行一个 set-user-ID 程序(即通过 exec() 加载到进程内存中)时,内核会将该进程的有效用户 ID 设置为与可执行文件的用户 ID 相同。运行 set-group-ID 程序对进程的有效组 ID 也有类似的效果。以这种方式更改有效用户或组 ID 会赋予进程(换句话说,就是执行该程序的用户)通常不会拥有的权限。例如,如果一个可执行文件归 root(超级用户)所有并且启用了 set-user-ID 权限位,那么当运行该程序时,进程将获得超级用户权限。

set-user-ID 和 set-group-ID 程序也可以设计为将进程的有效 ID 更改为除 root 之外的其他 ID。例如,为了访问受保护的文件(或其他系统资源),可能只需创建一个具有访问该文件所需权限的专用用户(组)ID,并创建一个 set-user-ID(set-group-ID)程序,将进程的有效用户(组)ID 更改为该 ID。这允许程序在不赋予超级用户所有权限的情况下访问该文件。

有时,我们会使用“set-user-ID-root”这个术语来区分由 root 拥有的 set-user-ID 程序和由其他用户拥有的程序,后者仅仅赋予进程该用户的权限。

我们现在已经开始以两种不同的意义使用“特权”(privileged)一词。一种是前面定义的意义:一个有效用户 ID 为 0 的进程,拥有所有授予 root 的权限。然而,当我们讨论由非 root 用户拥有的 set-user-ID 程序时,我们有时会将某个进程称为获得了该 set-user-ID 程序所属用户 ID 的权限。在每种情况下我们所指的“特权”含义应该从上下文中可以清楚地看出。

出于我们在第 38.3 节中解释的原因,在 Linux 上,set-user-ID 和 set-group-ID 权限位对 shell 脚本没有任何效果

在 Linux 上,常用的设置用户 ID(set-user-ID)程序的例子包括:passwd(1),用于更改用户密码;mount(8) 和 umount(8),用于挂载和卸载文件系统;以及 su(1),允许用户以不同的用户 ID 运行 shell。一个设置组 ID(set-group-ID)程序的例子是 wall(1),它将消息写入所有属于 tty 组的终端(通常每个终端都属于该组)。

在 8.5 节中,我们指出列出在清单 8-2 中的程序需要从 root 登录运行,以便能够访问 /etc/shadow 文件。我们可以通过将此程序设为 set-user-ID-root 程序,使其可以被任何用户运行,如下所示:

# chown root check_password Make this program owned by root # chmod u+s check_password With the set-user-ID bit enabled

设置用户ID/设置组ID的技术是一种有用且强大的工具,但在设计不良的应用程序中可能导致安全漏洞。在第38章中,我们列出了一套在编写设置用户ID和设置组ID程序时应遵循的良好实践。

9.4 Saved Set-User-lD and Saved Set-Group-lD

已保存的 set-user-ID 和已保存的 set-group-ID 是为与 set-user-ID 和 set-group-ID 程序一起使用而设计的。当程序被执行时,会发生以下步骤(以及其他许多步骤):

  1. 如果可执行文件上启用了 set-user-ID(set-group-ID)权限位,则进程的有效用户 ID(组 ID)将被设置为与可执行文件的所有者相同。如果未设置 set-user-ID(set-group-ID)位,则不会更改进程的有效用户 ID(组 ID)。
  2. 已保存的 set-user-ID 和已保存的 set-group-ID 的值将从相应的有效 ID 中复制。无论正在执行的文件是否设置了 set-user-ID 或 set-group-ID 位,此复制操作都会发生。

作为上述步骤效果的一个例子,假设一个进程的真实用户 ID、有效用户 ID 和保存的 set-user-ID 都是 1000,而它执行了一个由 root(用户 ID 为 0)拥有的 set-user-ID 程序。exec 后,该进程的用户 ID 将按如下方式更改:

real=1000effective=0saved=0

各种系统调用允许一个 set-user-ID 程序在实际用户 ID 和保存的 set-user-ID 值之间切换它的有效用户 ID。类似的系统调用允许 set-group-ID 程序修改它的有效组 ID。通过这种方式,程序可以临时放弃并恢复与 exec 文件的用户(组)ID 相关的任何特权。(换句话说,程序可以在潜在拥有特权与实际上使用特权操作的状态之间切换。)正如我们将在第 38.2 节中详细说明的,对于 set-user-ID 和 set-group-ID 程序,当程序实际上不需要执行任何与特权(即保存的 set)ID 相关的操作时,采用无特权(即实际)ID 运行是安全的编程实践。

保存的 set-user-ID 和保存的 set-group-ID 有时也同义地称为保存的用户 ID 和保存的组 ID。

保存的 set ID 是 System V 的发明,并被 POSIX 采纳。在 4.4 之前的 BSD 版本中并未提供这些功能。最初的 POSIX.1 标准将对这些 ID 的支持设为可选,但后来的标准(从 1988 年的 FIPS 151-1 开始)则将支持设为强制性。

💡 SUID是EUID的备份,便于EUID提升或降低权限。

9.5 File-System user ID and File-System Group ID

**在 Linux 上,用于执行文件系统操作(例如打开文件、更改文件所有权和修改文件权限)时,使用的是文件系统用户和组 ID,而不是有效用户和组 ID(并与附加组 ID 一起使用)。**有效 ID 仍然用于前面描述的其他目的,就像其他 UNIX 实现一样。

通常,文件系统的用户和组 ID 与相应的有效 ID 具有相同的值(因此通常与相应的真实 ID 相同)。此外,每当有效用户或组 ID 通过系统调用或执行 set-user-ID 或 set-group-ID 程序而发生改变时,相应的文件系统 ID 也会改变为相同的值。由于文件系统 ID 以这种方式跟随有效 ID,这意味着在检查权限和特权时,Linux 的行为实际上与其他 UNIX 实现相同。文件系统 ID 与相应的有效 ID 不同,因此 Linux 与其他 UNIX 实现的区别,仅在于使用两个 Linux 特有的系统调用 setfsuid() 和 setfsgid() 明确使它们不同的时候。

**为什么 Linux 提供文件系统 ID,以及在什么情况下我们希望有效 ID 与文件系统 ID 不同?**原因主要是历史性的。文件系统 ID 最早出现在 Linux 1.2 版本。在该内核版本中,如果发送进程的有效用户 ID 与目标进程的实际或有效用户 ID 相匹配,则该进程可以向另一个进程发送信号。这影响了一些程序,例如 Linux NFS(网络文件系统)服务器程序,它需要能够像拥有对应客户端进程的有效 ID 一样访问文件。但是,如果 NFS 服务器改变了它的有效用户 ID,它将容易受到非特权用户进程发送的信号攻击。为了防止这种可能性,引入了单独的文件系统用户和组 ID。通过保持其有效 ID 不变,但改变其文件系统 ID,NFS 服务器可以在访问文件时伪装成其他用户,同时不会受到用户进程信号的影响。

从内核 2.0 起,Linux 采用了 SUSv3 强制规定的发送信号权限规则,而这些规则不涉及目标进程的有效用户 ID(参见第 20.5 节)。因此,文件系统 ID 功能已不再严格必要(如今,一个进程可以通过本章后面描述的系统调用,巧妙地在需要时在非特权值和有效用户 ID 之间切换,从而达到所需效果),但它仍然保留以兼容现有软件。

由于文件系统 ID 有些奇特,而且它们通常与对应的有效 ID 值相同,因此在本书的其余部分,我们通常会以进程的有效 ID 来描述各种文件权限检查以及新文件所有权的设置。尽管在 Linux 上这些操作实际上是使用进程的文件系统 ID 来完成的,但在实践中,它们的存在很少会产生实际影响。

9.6 Supplementary Group IDs

补充组ID是一组进程所属的附加组。新进程从其父进程继承这些ID。登录Shell从系统组文件获取其补充组ID。如上所述,这些ID与有效ID和文件系统ID结合使用,以确定访问文件、System V IPC对象和其他系统资源的权限。

9.7 Retrieving and Modifying Process Credentials

Linux 提供了一系列系统调用和库函数,用于获取和更改本章中描述的各种用户和组 ID。其中只有部分 API 在 SUSv3 中有规定。在其余部分中,有一些在其他 UNIX 实现中也广泛可用,而少数是 Linux 特有的。我们在描述每个接口时会指出可移植性问题。在本章末尾,表 9-1 总结了所有用于更改进程凭证的接口的操作。

作为使用以下页面中描述的系统调用的替代方法,任何进程的凭证都可以通过检查 Linux 特有的 /proc/PID/status 文件中的 Uid、Gid 和 Groups 行来找到。Uid 和 Gid 行按照实际、有效、保存集以及文件系统的顺序列出标识符。

在以下各节中,我们使用传统的特权进程定义,即其有效用户 ID 为 0 的进程。然而,Linux 将超级用户权限的概念划分为不同的能力,如第 39 章所述。对于我们讨论的所有用于更改进程用户和组 ID 的系统调用,有两项能力是相关的:

  • CAP_SETUID 权限允许进程对其用户 ID 进行任意更改。
  • CAP_SETGID 权限允许进程对其组 ID 进行任意更改。

9.7.1 Retrieving and Modifying Real, Effective, and Saved Set IDs

接下来,我们将描述用于检索和修改真实、有效和保存的组 ID 的系统调用。有几个系统调用可以执行这些任务,在某些情况下,它们的功能会重叠,这反映了这些不同的系统调用最初源自不同的 UNIX 实现。

Retrieving real and effective IDs
getuid() 和 getgid() 系统调用分别返回调用进程的真实用户 ID 和真实组 ID。geteuid() 和 getegid() 系统调用则执行对应的有效 ID 任务。这些系统调用总是成功的。

#include<unistd.h>uid_tgetuid(void);Returns real user ID of calling processuid_tgeteuid(void);Returns effective user ID of calling processgid_tgetgid(void);Returns real group ID of calling processgid_tgetegid(void);Returns effective group ID of calling process

Modifying effective IDs
setuid() 系统调用将调用进程的有效用户 ID——并且可能的真实用户 ID 以及保存的设置用户 ID——更改为 uid 参数给定的值。setgid() 系统调用对相应的组 ID 执行类似的操作。

#include<unistd.h>intsetuid(uid_tuid);intsetgid(gid_tgid);

关于进程使用 setuid() 和 setgid() 修改其凭证的规则取决于进程是否具备特权(即有效用户 ID 是否为 0)。以下是关于 setuid() 的规则:

  1. 当非特权进程调用 setuid() 时,仅改变进程的有效用户 ID。此外,它只能被更改为与真实用户 ID 或保存的设置用户 ID 相同的值。(试图违反此限制会产生 EPERM 错误。)这意味着,对于非特权用户来说,此调用仅在执行 set-user-ID 程序时有用,因为对于普通程序的执行,进程的真实用户 ID、有效用户 ID 和保存的设置用户 ID 均具有相同的值。在一些源自 BSD 的实现中,非特权进程调用 setuid() 或 setgid() 的语义与其他 UNIX 实现不同:这些调用会更改真实、有效以及保存的设置 ID(为当前的真实或有效 ID 的值)。
  2. 当一个特权进程以非零参数执行 setuid() 时,实际用户 ID、有效用户 ID 和已保存的设置用户 ID 都会被设置为 uid 参数指定的值。这是一个单向操作,因为一旦特权进程以这种方式更改了其标识符,它将失去所有特权,因此随后不能再使用 setuid() 将标识符重置回 0。如果不希望如此,则应使用稍后描述的 seteuid() 或 setreuid() 来替代 setuid()。

使用 setgid() 修改组 ID 的规则与 setuid() 的类似,只是将 setgid() 替换为 setuid(),并将组替换为用户。对于这些更改,规则 1 完全适用。对于规则 2,由于更改组 ID 不会导致进程失去权限(权限由有效用户 ID 决定),特权程序可以使用 setgid() 自由地将组 ID 改为任何所需的值。

以下调用是首选方法,用于一个有效用户 ID 当前为 0 的 set-user-ID-root 程序,以不可撤销地放弃所有权限(通过将有效用户 ID 和保存的 set-user-ID 都设置为与真实用户 ID 相同的值):

if(setuid(getuid())==-1)errExit("setuid");

一个由非 root 用户拥有的 set-user-ID 程序可以使用 setuid() 在真实用户 ID 和保存的 set-user-ID 值之间切换有效用户 ID,以实现第 9.4 节中描述的安全原因。然而,对于此目的,seteuid() 更可取,因为无论 set-user-ID 程序是否由 root 拥有,它的效果都是相同的。

进程可以使用 seteuid() 将其有效用户 ID 更改为 euid 指定的值,并使用 setegid() 将其有效组 ID 更改为 egid 指定的值。

#include<unistd.h>intseteuid(uid_teuid);intsetegid(gid_tegid);

以下规则管理进程使用 seteuid() 和 setegid() 更改其有效 ID 的情况:

  1. 非特权进程只能将有效 ID 更改为与相应的真实 ID 或已保存的设置 ID 相同的值。(换句话说,对于非特权进程,seteuid() 和 setegid() 的效果与 setuid() 和 setgid() 相同,除了前面提到的 BSD 兼容性问题。)
  2. 特权进程可以将有效 ID 更改为任何值。如果特权进程使用 seteuid() 将其有效用户 ID 更改为非零值,则它将不再具有特权(但可能能够通过前一条规则重新获得特权)。

使用 seteuid() 是 set-user-ID 和 set-group-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");

最初源自 BSD,seteuid() 和 setegid() 现在在 SUSv3 中有规范,并出现在大多数 UNIX 实现中。

在较旧版本的 GNU C 库(glibc 2.0 及更早版本)中,seteuid(euid) 实现为 setreuid(–1, euid)。在现代版本的 glibc 中,seteuid(euid) 实现为 setresuid(–1, euid, –1)。(我们稍后将介绍 setreuid()、setresuid() 及它们的组相关函数。)两种实现都允许我们将 euid 指定为与当前有效用户 ID 相同的值(即不更改)。然而,SUSv3 并未指定 seteuid() 的这种行为,并且在其他一些 UNIX 实现中也不可能。通常,跨实现的这种潜在差异并不明显,因为在正常情况下,有效用户 ID 与实际用户 ID 或保存的 set-user-ID 相同。(在 Linux 上使有效用户 ID 同时不同于实际用户 ID 和保存的 set-user-ID 的唯一方法是使用非标准的 setresuid() 系统调用。)

在所有版本的 glibc(包括现代版本)中,setegid(egid) 实现为 setregid(–1, egid)。与 seteuid() 一样,这意味着我们可以将 egid 指定为与当前有效组 ID 相同的值,尽管 SUSv3 并未指定这一行为。这也意味着,如果将有效组 ID 设置为不同于当前实际组 ID 的值,setegid() 会更改保存的 set-group-ID。(对于使用 setreuid() 的旧版 seteuid() 实现,也适用类似的说明。)同样,这种行为在 SUSv3 中没有被指定。

Modifying real and effective IDs
setreuid() 系统调用允许调用进程独立地更改其真实用户 ID 和有效用户 ID 的值。setregid() 系统调用对真实组 ID 和有效组 ID 执行类似的操作。

#include<unistd.h>intsetreuid(uid_truid,uid_teuid);intsetregid(gid_trgid,gid_tegid);

这些系统调用的第一个参数是新的真实ID。第二个参数是新的有效ID。如果我们只想更改其中一个标识符,那么可以对另一个参数指定 -1。

最初源自BSD,setreuid() 和 setregid() 现在在SUSv3中有规范,并且在大多数UNIX实现中可用。

与本节中描述的其他系统调用一样,规则限制了我们使用 setreuid() 和 setregid() 可以进行的更改。我们从 setreuid() 的角度描述这些规则,同时理解 setregid() 是类似的,除非有特别说明:

  1. 非特权进程只能将真实用户ID设置为当前的真实用户ID(即不更改)或有效用户ID。有效用户ID只能设置为当前的真实用户ID、有效用户ID(即不更改)或已保存的set-user-ID。

    SUSv3 表示,对于非特权进程是否可以使用 setreuid() 将真实用户 ID 的值更改为当前的真实用户 ID、有效用户 ID 或保存的设置用户 ID,尚未指定,并且可以对真实用户 ID 进行的具体更改细节在不同实现中有所不同。

    SUSv3 对 setregid() 的行为描述略有不同:非特权进程可以将真实组 ID 设置为当前保存的设置组 ID 的值,或者将有效组 ID 设置为真实组 ID 或保存的设置组 ID 的当前值。同样,可以进行的具体更改在不同实现中有所不同。
  2. 特权进程可以对 ID 做任何更改。
  3. 对于特权和非特权进程,如果满足以下任一条件,保存的 set-user-ID 也将设置为与(新的)有效用户 ID 相同的值:
    a) ruid 不是 -1(即,真实用户 ID 正在被设置,即使设置为它已经具有的相同值),或者
    b) 有效用户 ID 正在被设置为调用之前真实用户 ID 的值以外的值。

    换句话说,如果一个进程使用 setreuid() 仅仅是为了将有效用户 ID 改为与当前实际用户 ID 相同的值,那么保存的设置用户 ID 将保持不变,并且之后对 setreuid()(或 seteuid())的调用可以将有效用户 ID 恢复到保存的设置用户 ID 值。(SUSv3 并未指定 setreuid() 和 setregid() 对保存的设置 ID 的影响,但 SUSv4 指定了这里描述的行为。)

第三条规则提供了一种方法,让一个 set-user-ID 程序永久放弃其特权,可以使用如下调用:

setreuid(getuid(),getuid());

💡 这里说的永久降权是相对于seteuid的临时降权而言的。适合的场景为非特权用户通过seteuid获取root权限后,执行特权操作,然后永久降权。之所以后续无法再升权,是因为saved EUID也被改为getuid(),而非0。

一个想要将自身用户和组凭证更改为任意值的 set-user-ID-root 进程,应首先调用 setregid(),然后再调用 setreuid()。如果调用顺序相反,则 setregid() 调用将失败,因为在调用 setreuid() 后程序将不再拥有特权。如果我们使用 setresuid() 和 setresgid()(下文描述)来实现此目的,也适用类似的说明。

直到包括 4.3BSD 在内的 BSD 版本都没有保存的 set-user-ID 和保存的 set-group-ID(这些现在由 SUSv3 强制要求)。相反,在 BSD 上,setreuid() 和 setregid() 允许进程通过交换真实 ID 和有效 ID 的值来放弃和重新获得特权。这产生了一个不利的副作用,即为了改变有效用户 ID,不得不改变真实用户 ID。

Retrieving real, effective, and saved set IDs
在大多数 UNIX 实现中,一个进程无法直接获取(或更新)其保存的 set-user-ID 和 set-group-ID。然而,Linux 提供了两个(非标准的)系统调用,允许我们正好做到这一点:getresuid() 和 getresgid()。

#define_GNU_SOURCE#include<unistd.h>intgetresuid(uid_t*ruid,uid_t*euid,uid_t*suid);intgetresgid(gid_t*rgid,gid_t*egid,gid_t*sgid);

getresuid() 系统调用返回调用进程的真实用户 ID、有效用户 ID 和保存的设置用户 ID 的当前值,并将这些值存储在其三个参数所指向的位置。getresgid() 系统调用对相应的组 ID 执行相同的操作。

Modifying real, effective, and saved set IDs
setresuid() 系统调用允许调用进程独立地更改其三个用户 ID 的值。每个用户 ID 的新值由系统调用的三个参数指定。setresgid() 系统调用对组 ID 执行类似的任务。

#define_GNU_SOURCE#include<unistd.h>intsetresuid(uid_truid,uid_teuid,uid_tsuid);intsetresgid(gid_trgid,gid_tegid,gid_tsgid);

如果我们不想更改所有的标识符,那么为某个参数指定 –1 会使相应的标识符保持不变。例如,以下调用等同于 seteuid(x):

setresuid(-1,x,-1);

关于 setresuid() 可以进行哪些更改的规则(setresgid() 类似)如下:

  1. 非特权进程可以将其真实用户ID、有权限用户ID和保存的设置用户ID中的任何一个设置为当前真实用户ID、有权限用户ID或保存的设置用户ID中的任意值。
  2. 特权进程可以对其真实用户ID、有权限用户ID和保存的设置用户ID进行任意更改。
  3. 无论调用是否对其他ID进行了任何更改,文件系统用户ID始终设置为与(可能新的)有权限用户ID相同的值。

对 setresuid() 和 setresgid() 的调用具有全有或全无的效果。要么所有请求的标识符都成功更改,要么一个都不更改。(对于本章中描述的其他更改多个标识符的系统调用,也适用同样的评论。)

尽管 setresuid() 和 setresgid() 提供了更直接的 API 来更改进程凭证,但我们无法在应用程序中可移植地使用它们;它们未在 SUSv3 中规定,并且仅在少数其他 UNIX 实现中可用。

9.7.2 Retrieving and Modifying File-System IDs

**所有前面描述的更改进程有效用户或组ID的系统调用也总是会改变相应的文件系统ID。**要独立于有效ID更改文件系统ID,我们必须使用两个特定于Linux的系统调用:setfsuid() 和 setfsgid()。

#include<sys/fsuid.h>intsetfsuid(uid_tfsuid);intsetfsgid(gid_tfsgid);

setfsuid() 系统调用将进程的文件系统用户 ID 更改为由 fsuid 指定的值。setfsgid() 系统调用将文件系统组 ID 更改为由 fsgid 指定的值。

同样,对于可以进行的更改类型有一定规则。setfsgid() 的规则类似于 setfsuid() 的规则,如下:

  1. 非特权进程可以将文件系统用户 ID 设置为当前的实际用户 ID、有效用户 ID、文件系统用户 ID(即不更改)或已保存的设置用户 ID。
  2. 特权进程可以将文件系统用户 ID 设置为任何值。

这些调用的实现有些粗糙。首先,没有相应的系统调用来获取文件系统 ID 的当前值。此外,这些系统调用不进行错误检查;如果非特权进程试图将其文件系统 ID 设置为不可接受的值,该尝试将被静默忽略。每个系统调用的返回值都是相应文件系统 ID 的先前值,无论调用成功还是失败。因此,我们确实有一种方法可以找出文件系统 ID 的当前值,但只能在尝试(成功或失败)更改它们的同时进行。

在 Linux 上不再需要使用 setfsuid() 和 setfsgid() 系统调用,并且在设计为可移植到其他 UNIX 实现的应用程序中应避免使用它们。

9.7.3 Retrieving and Modifying Supplementary Group IDs

getgroups() 系统调用返回调用进程当前所属的组集合,存储在 grouplist 指向的数组中。

#include<unistd.h>intgetgroups(intgidsetsize,gid_tgrouplist[]);

在 Linux 上,与大多数 UNIX 实现一样,getgroups() 仅返回调用进程的辅助组 ID。然而,SUSv3 也允许实现将调用进程的有效组 ID 包含在返回的组列表中。

调用程序必须分配 grouplist 数组,并在参数 gidsetsize 中指定其长度。成功完成后,getgroups() 返回放置在 grouplist 中的组 ID 数量。

如果进程所属的组数超过 gidsetsize,getgroups() 会返回错误 (EINVAL)。为了避免这种可能性,可以将 grouplist 数组的大小设置为比常量 NGROUPS_MAX(在 <limits.h> 中定义)、定义了进程可能所属的最大辅助组数,多一个(以便可移植地允许可能包含有效组 ID)。因此,我们可以如下声明 grouplist:

gid_tgrouplist[NGROUPS_MAX+1];

在 2.6.4 之前的 Linux 内核中,NGROUPS_MAX 的值为 32。从 2.6.4 内核开始,NGROUPS_MAX 的值为 65,536。

💡 NGROUPS_MAX 的定义可以在文件/usr/include/linux/limits.h 中找到。

💡 在gdb中,默认是不能打印宏的,除非你用-g3 编译。

应用程序还可以在运行时通过以下方式确定 NGROUPS_MAX 限制:

  • 调用 sysconf(_SC_NGROUPS_MAX)。(我们在第 11.2 节解释了 sysconf() 的使用。)
  • 从只读的、特定于 Linux 的 /proc/sys/kernel/ngroups_max 文件中读取限制。自 2.6.4 内核起提供此文件。
$cat/proc/sys/kernel/ngroups_max65536

或者,应用程序可以调用 getgroups() 并将 gidsetsize 指定为 0。在这种情况下,grouplist 不会被修改,但调用的返回值会给出该进程所属的组的数量。

使用这些运行时技术获得的值可以用来动态分配一个 grouplist 数组,以供将来的 getgroups() 调用使用。

一个有特权的进程可以使用 setgroups() 和 initgroups() 来更改其补充组 ID 集。

#define_BSD_SOURCE#include<grp.h>intsetgroups(size_tgidsetsize,constgid_t*grouplist);intinitgroups(constchar*user,gid_tgroup);

setgroups() 系统调用用数组 grouplist 中给定的集合替换调用进程的补充组 ID。参数 gidsetsize 指定数组参数 grouplist 中的组 ID 数量。

initgroups() 函数通过扫描 /etc/groups 并建立一个指定用户所属的所有组的列表来初始化调用进程的补充组 ID。此外,参数 group 指定的组 ID 也会被添加到进程的补充组 ID 集合中。

initgroups() 的主要使用者是创建登录会话的程序——例如 login(1),它在执行用户的登录 shell 之前设置各种进程属性。此类程序通常通过读取密码文件中用户记录的组 ID 字段来获取用于 group 参数的值。这有点令人困惑,因为密码文件中的组 ID 并不是真正的附加组,而是定义登录 shell 的初始真实用户 ID、有效用户 ID 和已保存的 set-user-ID。不过,这正是 initgroups() 通常的使用方式。

虽然不是 SUSv3 的一部分,但 setgroups() 和 initgroups() 在所有 UNIX 实现中都是可用的。

9.7.4 Summary of Calls for Modifying Process Credentials

表 9-1 总结了用于更改进程凭据的各种系统调用和库函数的效果。

图 9-1 提供了表 9-1 中相同信息的图形概览。该图从更改用户 ID 的调用角度展示了内容,但更改组 ID 的规则类似。


Figure 9-1: Effect of credential-changing functions on process user IDs

Table 9-1: Summary of interfaces used to change process credentials

InterfacePurpose and effect within unprivileged processPurpose and effect within privileged processPortability
setuid(u)
setgid(g)
将有效ID更改为与当前真实ID或已保存的设置ID相同的值将真实、有效和已保存的设置ID更改为任意(单一)值在SUSv3中有说明;BSD衍生版本具有不同的语义
seteuid(e)
setegid(e)
将有效ID更改为与当前真实ID或已保存的设置ID相同的值将有效 ID 更改为任意值在 SUSv3 中指定
setreuid(r, e)
setregid(r, e)
(独立地)将真实 ID 改为与当前真实 ID 或有效 ID 相同的值,并将有效 ID 改为与当前真实、有效或保存的集合 ID 相同的值(独立地)将真实ID和有效ID更改为任意值在SUSv3中指定,但操作在各实现中有所不同
setresuid(r, e, s)
setresgid(r, e, s)
(独立地)将真实、有效和保存的集合ID更改为与当前真实、有效或保存的集合ID相同的值(独立地) 将真实、有效和已保存的集合 ID 更改为任意值在SUSv3中不存在,仅存在于少数其他UNIX实现中
setfsuid(u)
setfsgid(u)
将文件系统 ID 更改为与当前实际、有效文件系统或已保存的集合 ID 相同的值将文件系统ID更改为任意值Linux专用
setgroups(n, l)不能从非特权进程调用将辅助组 ID 设置为任意值不是 SUSv3 标准,但在所有 UNIX 实现中可用

请注意表 9-1 的以下补充信息:

  • glibc 对 seteuid()(作为 setresuid(–1, e, –1))和 setegid()(作为 setregid(–1, e))的实现也允许将有效 ID 设置为它已经具有的相同值,但 SUSv3 中未指定这一点。setegid() 的实现还会在有效用户 ID 设置为与当前真实用户 ID 不同的值时更改已保存的组 ID。(SUSv3 没有规定 setegid() 会更改已保存的组 ID。)
  • 对特权和非特权进程调用 setreuid() 和 setregid() 时,如果 r 不为 –1,或者 e 被指定为调用前与真实 ID 不同的值,则已保存的用户 ID 或已保存的组 ID 也会设置为与(新的)有效 ID 相同的值。(SUSv3 没有规定 setreuid() 和 setregid() 会更改已保存的 ID。)
  • 每当有效用户(组)ID 被更改时,Linux 特定的文件系统用户(组)ID 也会更改为相同的值。
  • 调用 setresuid() 总是将文件系统用户 ID 修改为与有效用户 ID 相同的值,无论调用是否更改有效用户 ID。调用 setresgid() 对文件系统组 ID 也有类似的效果。

9.7.5 Example: Displaying Process Credentials

清单 9-1 中的程序使用前几页中描述的系统调用和库函数来检索进程的所有用户和组 ID,然后显示它们。

Listing 9-1: Display all process user and group IDs

// proccred/idshow.c// 代码略

运行输出为:

$ ./idshowUID:real=vagrant(1000);eff=vagrant(1000);saved=vagrant(1000);fs=vagrant(1000);GID:real=vagrant(1000);eff=vagrant(1000);saved=vagrant(1000);fs=vagrant(1000);Supplementarygroups(2): wheel(10)vagrant(1000)

9.8 Summary

每个进程都有若干相关的用户和组ID(凭证)。真实ID定义了进程的所有权。在大多数UNIX实现中,有效ID用于确定进程访问文件等资源时的权限。然而在Linux中,文件系统ID用于确定访问文件的权限,而有效ID用于其他权限检查。(因为文件系统ID通常与相应的有效ID值相同,因此在检查文件权限时,Linux的行为与其他UNIX实现相同。)进程的附加组ID是进程在权限检查中被视为成员的附加组集合。各种系统调用和库函数允许进程获取和更改其用户和组ID。

当运行一个设置用户ID的程序时,进程的有效用户ID被设置为文件所有者的ID。该机制允许用户在运行特定程序时假定另一个用户的身份,从而获得其权限。相应地,设置组ID程序会更改运行该程序的进程的有效组ID。保存的设置用户ID和保存的设置组ID允许设置用户ID和设置组ID程序临时放弃然后再次恢复权限。

用户ID 0 是特殊的。通常,只有一个用户帐户名为 root 拥有此用户ID。有效用户ID为0的进程具有特权——也就是说,它们在进程执行各种系统调用(例如那些用于任意更改各种进程用户和组ID的调用)时,会免于许多通常的权限检查。

2026年3月3日读完第一遍

9.9 Exercises

参见:TLPI 第9章 练习:Process Credentials

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/21 7:27:42

小程序和多商户平台的区别?

小程序和多商户平台的区别&#xff1f;小程序和多商户平台的核心区别&#xff0c;不在于技术形式&#xff0c;而在于&#xff1a;一个是应用载体&#xff0c;一个是商业模式。更直接地说&#xff0c;小程序是用来承载业务的工具&#xff0c;而多商户平台是组织多方交易的经营结…

作者头像 李华
网站建设 2026/4/21 7:26:17

vulhub系列-74-Hackable III(超详细)

免责声明&#xff1a;本文记录的是 Hackable III 渗透测试靶机 的解题过程&#xff0c;所有操作均在 本地授权环境 中进行。内容仅供 网络安全学习与防护研究 使用&#xff0c;请勿用于任何非法用途。读者应遵守《网络安全法》及相关法律法规&#xff0c;自觉维护网络空间安全。…

作者头像 李华
网站建设 2026/4/21 7:22:14

Windows右键菜单终极清理指南:用ContextMenuManager告别菜单臃肿

Windows右键菜单终极清理指南&#xff1a;用ContextMenuManager告别菜单臃肿 【免费下载链接】ContextMenuManager &#x1f5b1;️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否曾为Windows右键菜单的杂乱无…

作者头像 李华
网站建设 2026/4/21 7:17:15

3分钟搞定!百度网盘提取码智能查询终极指南

3分钟搞定&#xff01;百度网盘提取码智能查询终极指南 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 还在为百度网盘分享链接的提取码而烦恼吗&#xff1f;每次遇到需要密码的资源&#xff0c;都要在论坛、贴吧、评论区里翻…

作者头像 李华
网站建设 2026/4/21 7:15:17

GitHub 上的 CI/CD 怎么用?从 GitHub Actions 到一条可上线的流水线

先把概念弄清楚 在 GitHub 生态里&#xff0c;大家说的 CI/CD&#xff0c;绝大多数场景指的就是 GitHub Actions&#xff1a;用仓库里的 YAML 描述「什么时候跑什么命令」&#xff0c;在 GitHub 托管的 Runner&#xff08;或自建 Runner&#xff09;上自动执行。 我这篇文章按「…

作者头像 李华