当前位置: 首页 > article >正文

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升权的进程也属于特权进程。或者简单说特权进程就是EUID0。在第39章中我们描述了Linux对能力的实现这是一种将授予超级用户的权限划分为多个独立单元的方案可以独立启用和禁用这些单元。通常有效用户和组ID的值与相应的真实ID相同但有效ID可以在两种情况下取得不同的值。一种方式是通过第9.7节中讨论的系统调用。另一种方式是通过执行设置用户IDset-user-ID和设置组IDset-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 us a.out # chmod gs 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-IDset-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 上常用的设置用户 IDset-user-ID程序的例子包括passwd(1)用于更改用户密码mount(8) 和 umount(8)用于挂载和卸载文件系统以及 su(1)允许用户以不同的用户 ID 运行 shell。一个设置组 IDset-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 us 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 程序一起使用而设计的。当程序被执行时会发生以下步骤以及其他许多步骤如果可执行文件上启用了 set-user-IDset-group-ID权限位则进程的有效用户 ID组 ID将被设置为与可执行文件的所有者相同。如果未设置 set-user-IDset-group-ID位则不会更改进程的有效用户 ID组 ID。已保存的 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 将按如下方式更改real1000effective0saved0各种系统调用允许一个 set-user-ID 程序在实际用户 ID 和保存的 set-user-ID 值之间切换它的有效用户 ID。类似的系统调用允许 set-group-ID 程序修改它的有效组 ID。通过这种方式程序可以临时放弃并恢复与 exec 文件的用户组ID 相关的任何特权。换句话说程序可以在潜在拥有特权与实际上使用特权操作的状态之间切换。正如我们将在第 38.2 节中详细说明的对于 set-user-ID 和 set-group-ID 程序当程序实际上不需要执行任何与特权即保存的 setID 相关的操作时采用无特权即实际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 不变但改变其文件系统 IDNFS 服务器可以在访问文件时伪装成其他用户同时不会受到用户进程信号的影响。从内核 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 CredentialsLinux 提供了一系列系统调用和库函数用于获取和更改本章中描述的各种用户和组 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 IDsgetuid() 和 getgid() 系统调用分别返回调用进程的真实用户 ID 和真实组 ID。geteuid() 和 getegid() 系统调用则执行对应的有效 ID 任务。这些系统调用总是成功的。#includeunistd.huid_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 processModifying effective IDssetuid() 系统调用将调用进程的有效用户 ID——并且可能的真实用户 ID 以及保存的设置用户 ID——更改为 uid 参数给定的值。setgid() 系统调用对相应的组 ID 执行类似的操作。#includeunistd.hintsetuid(uid_tuid);intsetgid(gid_tgid);关于进程使用 setuid() 和 setgid() 修改其凭证的规则取决于进程是否具备特权即有效用户 ID 是否为 0。以下是关于 setuid() 的规则当非特权进程调用 setuid() 时仅改变进程的有效用户 ID。此外它只能被更改为与真实用户 ID 或保存的设置用户 ID 相同的值。试图违反此限制会产生 EPERM 错误。这意味着对于非特权用户来说此调用仅在执行 set-user-ID 程序时有用因为对于普通程序的执行进程的真实用户 ID、有效用户 ID 和保存的设置用户 ID 均具有相同的值。在一些源自 BSD 的实现中非特权进程调用 setuid() 或 setgid() 的语义与其他 UNIX 实现不同这些调用会更改真实、有效以及保存的设置 ID为当前的真实或有效 ID 的值。当一个特权进程以非零参数执行 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 指定的值。#includeunistd.hintseteuid(uid_teuid);intsetegid(gid_tegid);以下规则管理进程使用 seteuid() 和 setegid() 更改其有效 ID 的情况非特权进程只能将有效 ID 更改为与相应的真实 ID 或已保存的设置 ID 相同的值。换句话说对于非特权进程seteuid() 和 setegid() 的效果与 setuid() 和 setgid() 相同除了前面提到的 BSD 兼容性问题。特权进程可以将有效 ID 更改为任何值。如果特权进程使用 seteuid() 将其有效用户 ID 更改为非零值则它将不再具有特权但可能能够通过前一条规则重新获得特权。使用 seteuid() 是 set-user-ID 和 set-group-ID 程序暂时放弃然后重新获得权限的首选方法。这里有一个例子euidgeteuid();/* 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);最初源自 BSDseteuid() 和 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 IDssetreuid() 系统调用允许调用进程独立地更改其真实用户 ID 和有效用户 ID 的值。setregid() 系统调用对真实组 ID 和有效组 ID 执行类似的操作。#includeunistd.hintsetreuid(uid_truid,uid_teuid);intsetregid(gid_trgid,gid_tegid);这些系统调用的第一个参数是新的真实ID。第二个参数是新的有效ID。如果我们只想更改其中一个标识符那么可以对另一个参数指定 -1。最初源自BSDsetreuid() 和 setregid() 现在在SUSv3中有规范并且在大多数UNIX实现中可用。与本节中描述的其他系统调用一样规则限制了我们使用 setreuid() 和 setregid() 可以进行的更改。我们从 setreuid() 的角度描述这些规则同时理解 setregid() 是类似的除非有特别说明非特权进程只能将真实用户ID设置为当前的真实用户ID即不更改或有效用户ID。有效用户ID只能设置为当前的真实用户ID、有效用户ID即不更改或已保存的set-user-ID。SUSv3 表示对于非特权进程是否可以使用 setreuid() 将真实用户 ID 的值更改为当前的真实用户 ID、有效用户 ID 或保存的设置用户 ID尚未指定并且可以对真实用户 ID 进行的具体更改细节在不同实现中有所不同。SUSv3 对 setregid() 的行为描述略有不同非特权进程可以将真实组 ID 设置为当前保存的设置组 ID 的值或者将有效组 ID 设置为真实组 ID 或保存的设置组 ID 的当前值。同样可以进行的具体更改在不同实现中有所不同。特权进程可以对 ID 做任何更改。对于特权和非特权进程如果满足以下任一条件保存的 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#includeunistd.hintgetresuid(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 IDssetresuid() 系统调用允许调用进程独立地更改其三个用户 ID 的值。每个用户 ID 的新值由系统调用的三个参数指定。setresgid() 系统调用对组 ID 执行类似的任务。#define_GNU_SOURCE#includeunistd.hintsetresuid(uid_truid,uid_teuid,uid_tsuid);intsetresgid(gid_trgid,gid_tegid,gid_tsgid);如果我们不想更改所有的标识符那么为某个参数指定 –1 会使相应的标识符保持不变。例如以下调用等同于 seteuid(x):setresuid(-1,x,-1);关于 setresuid() 可以进行哪些更改的规则setresgid() 类似如下非特权进程可以将其真实用户ID、有权限用户ID和保存的设置用户ID中的任何一个设置为当前真实用户ID、有权限用户ID或保存的设置用户ID中的任意值。特权进程可以对其真实用户ID、有权限用户ID和保存的设置用户ID进行任意更改。无论调用是否对其他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()。#includesys/fsuid.hintsetfsuid(uid_tfsuid);intsetfsgid(gid_tfsgid);setfsuid() 系统调用将进程的文件系统用户 ID 更改为由 fsuid 指定的值。setfsgid() 系统调用将文件系统组 ID 更改为由 fsgid 指定的值。同样对于可以进行的更改类型有一定规则。setfsgid() 的规则类似于 setfsuid() 的规则如下非特权进程可以将文件系统用户 ID 设置为当前的实际用户 ID、有效用户 ID、文件系统用户 ID即不更改或已保存的设置用户 ID。特权进程可以将文件系统用户 ID 设置为任何值。这些调用的实现有些粗糙。首先没有相应的系统调用来获取文件系统 ID 的当前值。此外这些系统调用不进行错误检查如果非特权进程试图将其文件系统 ID 设置为不可接受的值该尝试将被静默忽略。每个系统调用的返回值都是相应文件系统 ID 的先前值无论调用成功还是失败。因此我们确实有一种方法可以找出文件系统 ID 的当前值但只能在尝试成功或失败更改它们的同时进行。在 Linux 上不再需要使用 setfsuid() 和 setfsgid() 系统调用并且在设计为可移植到其他 UNIX 实现的应用程序中应避免使用它们。9.7.3 Retrieving and Modifying Supplementary Group IDsgetgroups() 系统调用返回调用进程当前所属的组集合存储在 grouplist 指向的数组中。#includeunistd.hintgetgroups(intgidsetsize,gid_tgrouplist[]);在 Linux 上与大多数 UNIX 实现一样getgroups() 仅返回调用进程的辅助组 ID。然而SUSv3 也允许实现将调用进程的有效组 ID 包含在返回的组列表中。调用程序必须分配 grouplist 数组并在参数 gidsetsize 中指定其长度。成功完成后getgroups() 返回放置在 grouplist 中的组 ID 数量。如果进程所属的组数超过 gidsetsizegetgroups() 会返回错误 (EINVAL)。为了避免这种可能性可以将 grouplist 数组的大小设置为比常量 NGROUPS_MAX在 limits.h 中定义、定义了进程可能所属的最大辅助组数多一个以便可移植地允许可能包含有效组 ID。因此我们可以如下声明 grouplist:gid_tgrouplist[NGROUPS_MAX1];在 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#includegrp.hintsetgroups(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 IDsTable 9-1: Summary of interfaces used to change process credentialsInterfacePurpose and effect within unprivileged processPurpose and effect within privileged processPortabilitysetuid(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:realvagrant(1000);effvagrant(1000);savedvagrant(1000);fsvagrant(1000);GID:realvagrant(1000);effvagrant(1000);savedvagrant(1000);fsvagrant(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

相关文章:

TLPI 第9章 读书笔记:Process Credentials

笔记和练习博客总目录见:开始读TLPI。 每个进程都有一组关联的数字用户标识符(UID)和组标识符(GID)。有时,这些被称为进程凭证。这些标识符如下: 实际用户ID和组ID;有效用户ID和组…...

Golang怎么实现跳表数据结构_Golang如何用Skip List实现有序数据的快速查找【方法】

Go标准库未提供跳表,因map和sort.Slicesort.Search已覆盖多数有序场景;但需动态插入、保持有序且平均O(log n)查找时(如内存索引、延迟调度),须自研或引入第三方。为什么 Go 标准库没有 skip listGo 官方没提供跳表&am…...

基于 YOLOv11 的无人机航拍小目标检测系统 基于 YOLOv11 的无人机小目标检测系统,基于 VisDrone 2019 数据集,实现从模型训练、验证、推理到 PyQt6 桌面应用的完整流程。

智慧巡检-基于 YOLOv11 的无人机小目标检测系统,基于 VisDrone 2019 数据集,实现从模型训练、验证、推理到 PyQt6 桌面应用的完整流程。【核心亮点】 1、小目标优化:针对无人机航拍目标小、密集、多尺度等特点,支持 1280 高分辨率…...

新建工程2

我们把stm32最小开发板和stlink链接好后,开始进入keil。 打开魔术棒按钮选择debug,这个调试器默认为ulink。所以我们改为stlink debug。 然后点击旁边的setting按钮,在flash Download里把reset and run这一项勾上。(勾上这项后&a…...

vulhub系列-76-02-Breakout(超详细)

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

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

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

vulhub系列-73-RA1NXing Bots(超详细)

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

知识图谱(BILSTM+CRF项目完整实现)【第六章】

一、代码架构图在data_origin中有两种类型的数据:分别是一般项目和一般项目txtoriginal一般项目中放的是部位、症状、索引;列之间用制表符隔开一般项目txtoriginal放的是原始数据;二、构建序列标注数据要把原始数据转换为目标数据:常用的方式…...

LLM应用缓存设计范式重构,Dify 2026新增Context-Aware TTL引擎与动态驱逐策略

第一章:Dify 2026缓存机制演进与核心设计哲学Dify 2026 的缓存体系并非简单沿袭传统 LRU 或 TTL 模式,而是以“语义感知”与“推理链可追溯”为双支柱重构底层数据生命周期管理。其核心设计哲学强调:缓存不是性能的临时补丁,而是推…...

NativeScript APP 开发备忘

devtools 调试断开 命令ns debug android可以开启浏览器的调试页面,非常方便。一开始使用功能非常完整,包括元素、日志、代码和网络,后来用着用着,发现元素和网络没了,剩下日志和代码可用,再后来用着用着&…...

unity mcp接入 实现一句话生成游戏!

文章目录前言一、MCP 核心包接入 Unity 编辑器1、使用Git URL 安装(可选,最新)2、Unity Asset Store 安装(可选,稳定)2、OpenUPM(可选)二、Python 3.10 与 uv 环境搭建1、安装 Pyth…...

担心2026年数字人直播系统投入过高?五款主流平台落地方案对比评测

一、引文/摘要:投入焦虑下,如何选对数字人直播系统2026年数字人直播持续升温,越来越多商家想借助数字人直播系统降本增效,但“投入高、落地难、性价比低”成为首要顾虑。不少用户困惑,如何在控制成本的同时&#xff0c…...

多态章-虚函数-重写-协变-override/final-重写覆盖隐藏的对比-纯虚函数与抽象类-多态的底层-虚函数表-动态绑定-静态绑定

使用的父类子类 基于继承下的虚函数 调用 ——代码复用。形成条件:1.必须是基类的指针或引用调用虚函数。 2.调用子类中拥有父类的虚函数的重写/覆盖。虚函数:类成员函数前加以virtual就成为了虚函数 注意:非成员函数无法加virtual修饰。cl…...

Phi-3-mini-4k-instruct-gguf多场景应用:写邮件/解题/写SQL/生成测试用例实战演示

Phi-3-mini-4k-instruct-gguf多场景应用:写邮件/解题/写SQL/生成测试用例实战演示 1. 模型简介 Phi-3-Mini-4K-Instruct是一个38亿参数的轻量级开源模型,采用GGUF格式提供。这个模型在Phi-3数据集上训练,该数据集包含合成数据和经过筛选的公…...

Java八股文实战:从原理到代码,解析Pixel Couplet Gen的Java客户端设计

Java八股文实战:从原理到代码,解析Pixel Couplet Gen的Java客户端设计 1. 为什么需要关注Java客户端设计 在分布式系统开发中,客户端设计往往是被忽视的一环。很多开发者更关注服务端实现,却忽略了客户端的健壮性和可维护性。但…...

金融评分卡‌是一种将用户信用风险量化为分数的模型工具,广泛应用于贷款审批、额度定价和风险预警等环节,分数越高代表风险越低

‌金融评分卡‌是一种将用户信用风险量化为分数的模型工具,广泛应用于贷款审批、额度定价和风险预警等环节,分数越高代表风险越低。一、评分卡的核心作用金融机构通过评分卡快速判断:是否授信(如信用卡申请)授信额度与…...

0421晨间日记

- 关键词 - 上午- 吃饭- 从五台山到大同 - 下午- 云冈石窟- 石头要好雕刻,就意味着容易损毁- 国家要统治- 人生来就是苦的,让你接受是苦的- 地主因为信佛,得到了好处的,愿意捐钱修建- 大同古城墙- 这个建立起来确实很壮观- 但是高…...

数据预处理和超范围值处理步骤 18

1 .数据预处理实验(1)导入数据操作步骤:① 从“源”面板拖入“Excel”节点。② 双击节点,选择待处理的数据文件。③ 从“输出”面板拖入“表格”节点,连接至“Excel”节点,右键运行,查看原始数据…...

辅助医生能力成长与患者个体化治疗方案生成系统(上)

摘要 本文档详细阐述了一套面向基层医疗机构的辅助医生能力成长与患者个体化治疗方案生成系统的设计与实现。系统以“规则驱动为基、数据驱动为翼”为核心思想,通过症状-疾病映射、指南依据匹配、用药禁忌筛查、个体化调整与风险预警等模块,为临床医生提供实时、可解释的决策…...

【2026最新】JDK 下载安装与环境配置全教程(Windows/Mac/Linux 三平台,零基础友好)

Java 开发的第一步,就是把 JDK 环境搭好。这一步看着简单,但不少新手会在环境变量配置上踩坑——JAVA_HOME 没设对、javac 报“不是内部或外部命令”、改完变量终端里还是不生效……这些坑我都替你踩过一遍了。 这篇文章就用最直白的方式,手…...

在 Word 中,一个公式就能看出你会不会高效排版

在 Word 中,一个公式就能看出你会不会高效排版 很多人写论文、实验报告或者技术文档时,一碰到公式就习惯打开 MathType,点来点去插入分式、求和、下标,操作不算难,但确实有点慢。 其实,对于很多常见公式&am…...

从零开始:Spring Boot + MyBatis 搭建后端接口完整教程

前言:你是否刚接触 Spring Boot,面对一堆配置不知从何下手?是否看了很多教程,却还是搞不清 Controller、Service、Mapper 到底谁先谁后?本文带你从零开始,手把手搭建一个完整的 Spring Boot MyBatis 项目。…...

当智能眼镜遇上了AI——使用灵珠搭建【镜中食谱】智能体

今天带大家沉浸式体验 Rokid 自研的 AI 开发平台——【灵珠平台】! 🌟 零代码、零门槛,手把手教你搭建一个专属的【镜中食谱】智能体,让 Rokid Glasses 解决你的吃饭难题! 本文智能体基于Rokid AI Glasses和灵珠AI平…...

Pi0视觉-语言-动作流模型科研应用:人类意图识别与机器人行为对齐研究

Pi0视觉-语言-动作流模型科研应用:人类意图识别与机器人行为对齐研究 1. 项目概述与科研价值 Pi0是一个突破性的视觉-语言-动作流模型,专门为通用机器人控制而设计。这个模型的核心价值在于它能够将人类的自然语言指令、视觉感知和机器人动作生成无缝连…...

robot_localization实现imu和odom融合

记录使用robot_localization进行融合下载地址:git clone https://gitee.com/bingshuibuliang/robot_localization.git注意:/odometry/filtered是这个节点发送的融合位姿,修改的话需要在ekf_nodelet_template.launch里,在使用robot…...

从扩频时钟到弹性缓存:一张图看懂PCIe是如何‘容忍’时钟偏差,保证数据不丢的

从水流模型到数据同步:图解PCIe时钟偏差补偿机制 想象一下城市供水系统中两个不同步的水泵——一个抽水快,一个抽水慢。如果没有调节装置,要么水管爆裂,要么用户断水。PCIe总线面临的时钟同步挑战与此惊人相似。本文将用生活化的水…...

《Spring Boot 第一个 REST API 教程》

前置知识:Java 基础、Maven 基础 最终效果:启动一个 Spring Boot 应用,通过浏览器访问 http://localhost:8080/hello 得到 {"msg":"Hello World"} 步骤 1:创建项目 推荐使用 Spring Initializr:…...

ROS2笔记2:使用Topic自定义Messages实现nodes之间通讯

1. Package规划 1.1 自定义消息接口my_interface_pkg DirCommandRequest.msg请求Topic消息 由发起node发起的Topic消息builtin_interfaces/Time stamp # 记录时间戳 string directory # 操作的目录 int8 command # 操作指令. 0:list, 1:re…...

如何3分钟解决百度网盘提取码难题:baidupankey完全指南

如何3分钟解决百度网盘提取码难题:baidupankey完全指南 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾因找不到百度网盘提取码而错过重要资源?当朋友分享学习资料、同事发送工作文件&#xff…...

2026年最新好用的WMS仓库管理系统盘点!10款国内外热门WMS系统推荐

面对仓储管理的挑战,越来越多的企业开始关注WMS系统。但在选型时,很多企业面临同样的困惑:市场上WMS系统那么多,到底哪款适合自己?本文盘点2026年国内外10款热门WMS系统,从功能特点、适用场景、性价比等维度…...