82. getresuid - 获取进程的真实、有效和保存的用户ID 链接到标题
1. 函数介绍 链接到标题
getresuid
是一个 Linux 系统调用,用于同时获取当前进程的真实用户 ID(Real User ID)、有效用户 ID(Effective User ID)和保存的设置用户 ID(Saved Set-user-ID)。这三个 ID 构成了 Unix/Linux 系统中完整的用户身份管理体系。
2. 函数原型 链接到标题
#include <unistd.h>
#include <sys/types.h>
int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);
83. getresgid - 获取进程的真实、有效和保存的组ID 链接到标题
1. 函数介绍 链接到标题
getresgid
是一个 Linux 系统调用,用于同时获取当前进程的真实组 ID(Real Group ID)、有效组 ID(Effective Group ID)和保存的设置组 ID(Saved Set-group-ID)。
2. 函数原型 链接到标题
#include <unistd.h>
#include <sys/types.h>
int getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid);
3. 功能对比 链接到标题
函数 | 功能 | 参数 |
---|---|---|
getresuid(ruid, euid, suid) |
获取用户 ID 三元组 | 3个 uid_t* 指针 |
getresgid(rgid, egid, sgid) |
获取组 ID 三元组 | 3个 gid_t* 指针 |
4. 参数说明 链接到标题
getresuid 参数:
uid_t *ruid
: 指向存储真实用户 ID 的变量的指针uid_t *euid
: 指向存储有效用户 ID 的变量的指针uid_t *suid
: 指向存储保存的设置用户 ID 的变量的指针
getresgid 参数:
gid_t *rgid
: 指向存储真实组 ID 的变量的指针gid_t *egid
: 指向存储有效组 ID 的变量的指针gid_t *sgid
: 指向存储保存的设置组 ID 的变量的指针
5. 返回值 链接到标题
- 成功时:返回 0
- 失败时:返回 -1,并设置
errno
6. 常见 errno 错误码 链接到标题
EFAULT
: 指针参数指向无效内存地址
7. 相似函数,或关联函数 链接到标题
getuid()
,geteuid()
: 分别获取真实和有效用户 IDgetgid()
,getegid()
: 分别获取真实和有效组 IDsetresuid()
,setresgid()
: 设置用户/组 ID 三元组setreuid()
,setregid()
: 设置真实和有效 IDsetuid()
,setgid()
: 设置用户/组 IDseteuid()
,setegid()
: 设置有效用户/组 ID
8. 示例代码 链接到标题
示例1:基本使用 - 获取完整的 ID 信息 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>
void print_user_info(const char *label, uid_t uid) {
printf("%-20s %d", label, uid);
struct passwd *pwd = getpwuid(uid);
if (pwd != NULL) {
printf(" (%s)", pwd->pw_name);
}
printf("\n");
}
void print_group_info(const char *label, gid_t gid) {
printf("%-20s %d", label, gid);
struct group *grp = getgrgid(gid);
if (grp != NULL) {
printf(" (%s)", grp->gr_name);
}
printf("\n");
}
int main() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
int ret;
printf("=== 进程完整身份信息 ===\n");
// 获取用户 ID 三元组
ret = getresuid(&ruid, &euid, &suid);
if (ret == -1) {
perror("getresuid 失败");
exit(EXIT_FAILURE);
}
printf("用户 ID 信息:\n");
print_user_info("真实用户 ID:", ruid);
print_user_info("有效用户 ID:", euid);
print_user_info("保存的设置 UID:", suid);
// 获取组 ID 三元组
ret = getresgid(&rgid, &egid, &sgid);
if (ret == -1) {
perror("getresgid 失败");
exit(EXIT_FAILURE);
}
printf("\n组 ID 信息:\n");
print_group_info("真实组 ID:", rgid);
print_group_info("有效组 ID:", egid);
print_group_info("保存的设置 GID:", sgid);
// 分析身份状态
printf("\n身份状态分析:\n");
if (euid == 0) {
printf("✓ 当前进程具有 root 用户权限\n");
}
if (egid == 0) {
printf("✓ 当前进程具有 root 组权限\n");
}
if (ruid != euid) {
printf("✓ 用户身份已被切换 (真实: %d, 有效: %d)\n", ruid, euid);
}
if (rgid != egid) {
printf("✓ 组身份已被切换 (真实: %d, 有效: %d)\n", rgid, egid);
}
if (suid != euid) {
printf("✓ 保存的设置 UID 可用于权限恢复 (%d -> %d)\n", suid, euid);
}
if (sgid != egid) {
printf("✓ 保存的设置 GID 可用于权限恢复 (%d -> %d)\n", sgid, egid);
}
return 0;
}
示例2:错误处理和权限分析 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <string.h>
void safe_getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) {
if (getresuid(ruid, euid, suid) == -1) {
printf("getresuid 失败: %s\n", strerror(errno));
*ruid = *euid = *suid = (uid_t)-1;
}
}
void safe_getresgid(gid_t *rgid, gid_t *egid, gid_t *sgid) {
if (getresgid(rgid, egid, sgid) == -1) {
printf("getresgid 失败: %s\n", strerror(errno));
*rgid = *egid = *sgid = (gid_t)-1;
}
}
void analyze_privilege_status() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
printf("=== 权限状态详细分析 ===\n");
safe_getresuid(&ruid, &euid, &suid);
safe_getresgid(&rgid, &egid, &sgid);
printf("用户权限分析:\n");
printf(" 真实 UID: %d\n", ruid);
printf(" 有效 UID: %d", euid);
if (euid == 0) printf(" (ROOT 权限)");
printf("\n");
printf(" 保存 SUID: %d", suid);
if (suid == 0) printf(" (保存 ROOT 权限)");
printf("\n");
printf("\n组权限分析:\n");
printf(" 真实 GID: %d\n", rgid);
printf(" 有效 GID: %d", egid);
if (egid == 0) printf(" (ROOT 组权限)");
printf("\n");
printf(" 保存 SGID: %d", sgid);
if (sgid == 0) printf(" (保存 ROOT 组权限)");
printf("\n");
// 权限切换能力分析
printf("\n权限切换能力:\n");
if (suid != euid) {
printf("✓ 可以通过 setuid() 恢复到保存的 UID %d\n", suid);
}
if (sgid != egid) {
printf("✓ 可以通过 setgid() 恢复到保存的 GID %d\n", sgid);
}
// 特权状态
if (euid == 0 || egid == 0) {
printf("⚠ 当前进程具有特权权限,注意安全操作\n");
}
if ((euid != ruid || egid != rgid) && (suid == ruid && sgid == rgid)) {
printf("✓ 可以通过保存的 ID 完全恢复到原始身份\n");
}
}
void demonstrate_invalid_pointer_handling() {
printf("\n=== 无效指针处理测试 ===\n");
// 测试 NULL 指针
if (getresuid(NULL, NULL, NULL) == -1) {
if (errno == EFAULT) {
printf("✓ 正确处理了 NULL 指针 (EFAULT)\n");
} else {
printf("✗ 意外错误: %s\n", strerror(errno));
}
}
// 测试部分 NULL 指针
uid_t ruid, euid;
if (getresuid(&ruid, &euid, NULL) == -1) {
if (errno == EFAULT) {
printf("✓ 正确处理了部分 NULL 指针\n");
}
}
}
int main() {
analyze_privilege_status();
demonstrate_invalid_pointer_handling();
return 0;
}
示例3:权限切换和恢复演示 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
void print_current_ids(const char *context) {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
printf("获取 ID 信息失败\n");
return;
}
printf("%s:\n", context);
printf(" UID: %d/%d/%d (真实/有效/保存)\n", ruid, euid, suid);
printf(" GID: %d/%d/%d (真实/有效/保存)\n", rgid, egid, sgid);
printf("\n");
}
int main() {
printf("=== 权限切换和恢复演示 ===\n");
// 初始状态
print_current_ids("初始状态");
// 检查是否具有特权权限
uid_t euid;
getresuid(NULL, &euid, NULL);
if (euid != 0) {
printf("注意: 当前进程不是 root,某些权限操作可能失败\n");
printf("建议以 root 权限运行此演示\n");
}
// 演示 setuid/setgid 的效果
printf("尝试进行权限操作...\n");
// 如果是 root,可以进行权限切换演示
if (euid == 0) {
// 切换到 nobody 用户(假设 UID 65534)
uid_t nobody_uid = 65534;
gid_t nogroup_gid = 65534;
printf("尝试切换到 nobody 用户...\n");
if (setresuid(nobody_uid, nobody_uid, nobody_uid) == 0 &&
setresgid(nogroup_gid, nogroup_gid, nogroup_gid) == 0) {
print_current_ids("切换到 nobody 后");
printf("✓ 成功切换到 nobody 用户\n");
} else {
printf("✗ 切换失败: %s\n", strerror(errno));
}
// 尝试恢复权限(应该失败,因为保存的 ID 已改变)
if (setuid(0) == -1) {
printf("✓ 无法恢复到 root 权限(保存的 ID 已改变)\n");
}
} else {
printf("跳过权限切换演示(需要 root 权限)\n");
}
// 显示最终状态
print_current_ids("最终状态");
return 0;
}
示例4:安全审计和监控工具 链接到标题
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
typedef struct {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
time_t timestamp;
pid_t pid;
} identity_snapshot_t;
int capture_identity_snapshot(identity_snapshot_t *snapshot) {
snapshot->pid = getpid();
snapshot->timestamp = time(NULL);
if (getresuid(&snapshot->ruid, &snapshot->euid, &snapshot->suid) == -1 ||
getresgid(&snapshot->rgid, &snapshot->egid, &snapshot->sgid) == -1) {
return -1;
}
return 0;
}
void print_identity_snapshot(const identity_snapshot_t *snapshot) {
char time_str[32];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S",
localtime(&snapshot->timestamp));
printf("时间: %s\n", time_str);
printf("进程: %d\n", snapshot->pid);
printf("用户 ID: %d/%d/%d (真实/有效/保存)\n",
snapshot->ruid, snapshot->euid, snapshot->suid);
printf("组 ID: %d/%d/%d (真实/有效/保存)\n",
snapshot->rgid, snapshot->egid, snapshot->sgid);
// 显示用户名和组名
struct passwd *pwd = getpwuid(snapshot->ruid);
if (pwd) printf("真实用户: %s\n", pwd->pw_name);
pwd = getpwuid(snapshot->euid);
if (pwd) printf("有效用户: %s\n", pwd->pw_name);
struct group *grp = getgrgid(snapshot->rgid);
if (grp) printf("真实组: %s\n", grp->gr_name);
grp = getgrgid(snapshot->egid);
if (grp) printf("有效组: %s\n", grp->gr_name);
}
void security_audit() {
identity_snapshot_t snapshot;
printf("=== 安全审计报告 ===\n");
if (capture_identity_snapshot(&snapshot) == -1) {
printf("获取身份信息失败\n");
return;
}
print_identity_snapshot(&snapshot);
// 安全检查
printf("\n安全检查结果:\n");
// 检查特权权限
if (snapshot.euid == 0) {
printf("⚠ 警告: 进程以 root 权限运行\n");
}
if (snapshot.egid == 0) {
printf("⚠ 警告: 进程具有 root 组权限\n");
}
// 检查权限不一致
if (snapshot.ruid != snapshot.euid) {
printf("ℹ 信息: 用户权限已被切换\n");
}
if (snapshot.rgid != snapshot.egid) {
printf("ℹ 信息: 组权限已被切换\n");
}
// 检查保存的权限
if (snapshot.suid == 0 && snapshot.euid != 0) {
printf("ℹ 信息: 保存了 root 用户权限,可用于恢复\n");
}
if (snapshot.sgid == 0 && snapshot.egid != 0) {
printf("ℹ 信息: 保存了 root 组权限,可用于恢复\n");
}
// 检查潜在安全风险
if ((snapshot.ruid != snapshot.euid || snapshot.rgid != snapshot.egid) &&
(snapshot.suid == snapshot.ruid && snapshot.sgid == snapshot.rgid)) {
printf("✓ 安全: 可以完全恢复到原始身份\n");
}
}
void monitor_privilege_changes() {
printf("\n=== 权限变化监控 ===\n");
identity_snapshot_t initial, current;
if (capture_identity_snapshot(&initial) == -1) {
printf("无法获取初始身份信息\n");
return;
}
printf("监控 5 秒钟内的权限变化...\n");
for (int i = 0; i < 5; i++) {
sleep(1);
if (capture_identity_snapshot(¤t) == 0) {
// 检查是否有变化
if (current.ruid != initial.ruid ||
current.euid != initial.euid ||
current.suid != initial.suid ||
current.rgid != initial.rgid ||
current.egid != initial.egid ||
current.sgid != initial.sgid) {
printf("检测到权限变化:\n");
printf("之前: UID(%d/%d/%d) GID(%d/%d/%d)\n",
initial.ruid, initial.euid, initial.suid,
initial.rgid, initial.egid, initial.sgid);
printf("现在: UID(%d/%d/%d) GID(%d/%d/%d)\n",
current.ruid, current.euid, current.suid,
current.rgid, current.egid, current.sgid);
// 更新初始状态
initial = current;
}
}
}
printf("监控结束\n");
}
int main() {
security_audit();
monitor_privilege_changes();
return 0;
}
9. ID 类型说明 链接到标题
Unix/Linux 系统中的三类 ID:
// 用户 ID 三元组
ruid // Real User ID: 启动进程的用户
euid // Effective User ID: 当前权限检查使用的用户 ID
suid // Saved Set-user-ID: 保存的设置用户 ID
// 组 ID 三元组
rgid // Real Group ID: 启动进程的组
egid // Effective Group ID: 当前权限检查使用的组 ID
sgid // Saved Set-group-ID: 保存的设置组 ID
10. 实际应用场景 链接到标题
场景1:权限管理工具 链接到标题
int can_drop_privileges_completely() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
return 0;
}
// 检查是否可以完全丢弃特权
return (ruid == euid && euid == suid &&
rgid == egid && egid == sgid);
}
场景2:安全审计 链接到标题
void audit_process_privileges() {
identity_snapshot_t snapshot;
if (capture_identity_snapshot(&snapshot) == 0) {
if (snapshot.euid == 0) {
syslog(LOG_WARNING, "进程 %d 以 root 权限运行", snapshot.pid);
}
}
}
场景3:权限恢复 链接到标题
int restore_original_privileges() {
uid_t ruid, euid, suid;
gid_t rgid, egid, sgid;
if (getresuid(&ruid, &euid, &suid) == -1 ||
getresgid(&rgid, &egid, &sgid) == -1) {
return -1;
}
// 恢复到原始身份
return setresuid(ruid, ruid, ruid) || setresgid(rgid, rgid, rgid);
}
11. 注意事项 链接到标题
使用 getresuid
和 getresgid
时需要注意:
- 指针有效性: 所有指针参数必须指向有效的内存地址
- 错误处理: 虽然很少失败,但仍需检查返回值
- 权限检查: 获取其他进程的 ID 信息可能需要权限
- 并发安全: 在多线程环境中注意数据一致性
- 系统兼容: 在所有现代 Unix/Linux 系统中都可用
总结 链接到标题
getresuid
和 getresgid
是管理进程身份信息的重要函数:
关键特性:
- 完整信息: 一次性获取所有相关的 ID 信息
- 原子操作: 保证获取的 ID 组是一致的
- 安全相关: 是权限管理和安全审计的基础
- 系统调用: 直接访问内核信息,性能良好
主要应用:
- 安全审计和监控工具
- 权限管理和切换程序
- 系统管理和调试工具
- 容器和虚拟化环境中的权限控制
正确理解和使用这些函数对于编写安全、可靠的 Unix/Linux 程序至关重要,特别是在需要进行权限管理和安全检查的场景中。