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(): 分别获取真实和有效用户 ID
  • getgid(), getegid(): 分别获取真实和有效组 ID
  • setresuid(), setresgid(): 设置用户/组 ID 三元组
  • setreuid(), setregid(): 设置真实和有效 ID
  • setuid(), setgid(): 设置用户/组 ID
  • seteuid(), 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(&current) == 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. 注意事项 見出しへのリンク

使用 getresuidgetresgid 时需要注意:

  1. 指针有效性: 所有指针参数必须指向有效的内存地址
  2. 错误处理: 虽然很少失败,但仍需检查返回值
  3. 权限检查: 获取其他进程的 ID 信息可能需要权限
  4. 并发安全: 在多线程环境中注意数据一致性
  5. 系统兼容: 在所有现代 Unix/Linux 系统中都可用

总结 見出しへのリンク

getresuidgetresgid 是管理进程身份信息的重要函数:

关键特性:

  1. 完整信息: 一次性获取所有相关的 ID 信息
  2. 原子操作: 保证获取的 ID 组是一致的
  3. 安全相关: 是权限管理和安全审计的基础
  4. 系统调用: 直接访问内核信息,性能良好

主要应用:

  1. 安全审计和监控工具
  2. 权限管理和切换程序
  3. 系统管理和调试工具
  4. 容器和虚拟化环境中的权限控制

正确理解和使用这些函数对于编写安全、可靠的 Unix/Linux 程序至关重要,特别是在需要进行权限管理和安全检查的场景中。