getgroups系统调用及示例

getgroups – 获取进程的补充组列表

1. 函数介绍

getgroups – 获取进程的补充组列表

getgroups 是一个 Linux 系统调用,用于获取当前进程所属的补充组(supplementary groups)列表。除了进程的主要组 ID(由 getgid() 返回)之外,进程还可以属于多个补充组,这些组决定了进程对文件和资源的额外访问权限。

补充组机制是 Unix/Linux 系统中实现灵活访问控制的重要组成部分,允许用户同时属于多个组以获得相应的权限。

2. 函数原型

#include <unistd.h>
#include <sys/types.h>

int getgroups(int size, gid_t list[]);

3. 功能

获取当前进程所属的补充组 ID 列表。补充组是除了主要组之外,进程还属于的其他组。

4. 参数

  • int size: 指定 list 数组的大小(元素个数)
    • 如果为 0:函数返回补充组的数量,不填充 list 数组
    • 如果大于 0:将补充组 ID 填充到 list 数组中
  • gid_t list[]: 指向存储组 ID 的数组
    • 如果 size 为 0:可以为 NULL
    • 如果 size 大于 0:必须指向有效的数组

5. 返回值

  • 成功时:
    • 如果 size 为 0:返回补充组的数量
    • 如果 size 大于 0:返回实际填充到数组中的组 ID 数量
  • 失败时返回 -1,并设置 errno

6. 常见 errno 错误码

  • EINVALsize 参数小于补充组的实际数量(缓冲区不足)
  • EFAULTlist 指针指向无效内存地址
  • EPERM: 在某些安全限制下可能返回(较少见)

7. 相似函数,或关联函数

  • setgroups(): 设置进程的补充组列表
  • initgroups(): 根据用户信息初始化补充组列表
  • getgid(): 获取进程的真实组 ID
  • getegid(): 获取进程的有效组 ID
  • getuid()geteuid(): 获取用户 ID 相关函数
  • setgid()setegid(): 设置组 ID
  • getgrouplist(): 获取用户的所有组(包括主要组)
  • /etc/group: 系统组信息文件
  • /etc/passwd: 用户主要组信息文件

8. 示例代码

示例1:基本使用 – 获取补充组列表

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

int main() {
    int group_count;
    gid_t *group_list;
    long max_groups;
    
    printf("=== 获取进程补充组列表 ===\n");
    
    // 首先获取补充组数量
    group_count = getgroups(0, NULL);
    if (group_count == -1) {
        perror("获取补充组数量失败");
        exit(EXIT_FAILURE);
    }
    
    printf("补充组数量: %d\n", group_count);
    
    if (group_count == 0) {
        printf("当前进程没有补充组\n");
        return 0;
    }
    
    // 获取系统支持的最大组数
    max_groups = sysconf(_SC_NGROUPS_MAX);
    if (max_groups == -1) {
        max_groups = 65536;  // 设置一个较大的默认值
    }
    
    printf("系统最大支持组数: %ld\n", max_groups);
    
    // 确保不会超出系统限制
    if (group_count > max_groups) {
        printf("警告: 补充组数量超出系统限制\n");
        group_count = max_groups;
    }
    
    // 分配内存存储组列表
    group_list = malloc(group_count * sizeof(gid_t));
    if (group_list == NULL) {
        perror("内存分配失败");
        exit(EXIT_FAILURE);
    }
    
    // 获取补充组列表
    int result = getgroups(group_count, group_list);
    if (result == -1) {
        perror("获取补充组列表失败");
        free(group_list);
        exit(EXIT_FAILURE);
    }
    
    printf("成功获取 %d 个补充组:\n", result);
    printf("%-8s %-10s %s\n", "序号", "组ID", "组名");
    printf("%-8s %-10s %s\n", "----", "----", "----");
    
    // 显示每个组的信息
    for (int i = 0; i < result; i++) {
        printf("%-8d %-10d ", i + 1, group_list[i]);
        
        // 尝试获取组名
        struct group *grp = getgrgid(group_list[i]);
        if (grp != NULL) {
            printf("%s", grp->gr_name);
        } else {
            printf("(未知)");
        }
        printf("\n");
    }
    
    // 显示主要组信息进行对比
    gid_t primary_gid = getgid();
    printf("\n主要组信息:\n");
    printf("  组ID: %d\n", primary_gid);
    
    struct group *primary_grp = getgrgid(primary_gid);
    if (primary_grp != NULL) {
        printf("  组名: %s\n", primary_grp->gr_name);
    }
    
    // 检查主要组是否在补充组列表中
    int found_in_supplementary = 0;
    for (int i = 0; i < result; i++) {
        if (group_list[i] == primary_gid) {
            found_in_supplementary = 1;
            break;
        }
    }
    
    printf("主要组是否在补充组中: %s\n", 
           found_in_supplementary ? "是" : "否");
    
    free(group_list);
    return 0;
}

示例2:错误处理和边界情况

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <errno.h>
#include <string.h>

void demonstrate_error_handling() {
    int result;
    gid_t small_buffer[2];  // 故意使用小缓冲区
    
    printf("=== 错误处理演示 ===\n");
    
    // 获取实际补充组数量
    int actual_count = getgroups(0, NULL);
    if (actual_count == -1) {
        perror("获取补充组数量失败");
        return;
    }
    
    printf("实际补充组数量: %d\n", actual_count);
    
    if (actual_count > 0) {
        // 使用过小的缓冲区,应该返回错误
        printf("使用过小缓冲区测试 (大小: 2):\n");
        result = getgroups(2, small_buffer);
        
        if (result == -1) {
            if (errno == EINVAL) {
                printf("  错误处理正确: 缓冲区不足 (EINVAL)\n");
            } else {
                printf("  其他错误: %s\n", strerror(errno));
            }
        } else {
            printf("  意外成功,返回数量: %d\n", result);
        }
        
        // 使用 NULL 指针但 size > 0
        printf("使用 NULL 指针测试:\n");
        result = getgroups(10, NULL);
        if (result == -1) {
            if (errno == EFAULT) {
                printf("  错误处理正确: 无效指针 (EFAULT)\n");
            } else {
                printf("  其他错误: %s\n", strerror(errno));
            }
        }
    }
}

void demonstrate_empty_groups() {
    printf("\n=== 空组列表演示 ===\n");
    
    // 获取补充组数量
    int count = getgroups(0, NULL);
    if (count == -1) {
        perror("获取组数量失败");
        return;
    }
    
    printf("当前进程补充组数量: %d\n", count);
    
    if (count == 0) {
        printf("进程没有补充组权限\n");
        printf("这可能表示:\n");
        printf("  1. 进程以最小权限运行\n");
        printf("  2. 用户不属于任何补充组\n");
        printf("  3. 在容器或受限环境中运行\n");
    }
}

int main() {
    demonstrate_error_handling();
    demonstrate_empty_groups();
    return 0;
}

示例3:完整的组权限分析工具

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <string.h>

typedef struct {
    gid_t gid;
    char group_name[256];
    int is_primary;
    int is_effective;
    int is_supplementary;
} group_info_t;

int compare_gids(const void *a, const void *b) {
    gid_t gid_a = ((group_info_t*)a)->gid;
    gid_t gid_b = ((group_info_t*)b)->gid;
    return (gid_a > gid_b) - (gid_a < gid_b);
}

void analyze_process_groups() {
    gid_t primary_gid, effective_gid;
    int sup_count;
    gid_t *sup_groups = NULL;
    group_info_t *all_groups = NULL;
    int total_groups = 0;
    
    printf("=== 进程组权限完整分析 ===\n");
    
    // 获取基本信息
    primary_gid = getgid();
    effective_gid = getegid();
    
    printf("进程基本信息:\n");
    printf("  真实用户 ID: %d\n", getuid());
    printf("  有效用户 ID: %d\n", geteuid());
    printf("  真实组 ID: %d\n", primary_gid);
    printf("  有效组 ID: %d\n", effective_gid);
    
    // 获取用户信息
    struct passwd *pwd = getpwuid(getuid());
    if (pwd != NULL) {
        printf("  用户名: %s\n", pwd->pw_name);
    }
    
    // 获取补充组
    sup_count = getgroups(0, NULL);
    if (sup_count > 0) {
        sup_groups = malloc(sup_count * sizeof(gid_t));
        if (sup_groups == NULL) {
            perror("内存分配失败");
            return;
        }
        
        if (getgroups(sup_count, sup_groups) == -1) {
            perror("获取补充组失败");
            free(sup_groups);
            return;
        }
    }
    
    // 创建完整的组信息列表
    total_groups = 2 + sup_count;  // primary + effective + supplementary
    all_groups = malloc(total_groups * sizeof(group_info_t));
    if (all_groups == NULL) {
        perror("内存分配失败");
        if (sup_groups) free(sup_groups);
        return;
    }
    
    int index = 0;
    
    // 添加主要组
    all_groups[index].gid = primary_gid;
    all_groups[index].is_primary = 1;
    all_groups[index].is_effective = (primary_gid == effective_gid);
    all_groups[index].is_supplementary = 0;
    struct group *grp = getgrgid(primary_gid);
    if (grp != NULL) {
        strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
    } else {
        snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", primary_gid);
    }
    index++;
    
    // 添加有效组(如果不同于主要组)
    if (effective_gid != primary_gid) {
        all_groups[index].gid = effective_gid;
        all_groups[index].is_primary = 0;
        all_groups[index].is_effective = 1;
        all_groups[index].is_supplementary = 0;
        struct group *grp = getgrgid(effective_gid);
        if (grp != NULL) {
            strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
        } else {
            snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", effective_gid);
        }
        index++;
    }
    
    // 添加补充组
    for (int i = 0; i < sup_count; i++) {
        // 检查是否已存在
        int exists = 0;
        for (int j = 0; j < index; j++) {
            if (all_groups[j].gid == sup_groups[i]) {
                all_groups[j].is_supplementary = 1;
                exists = 1;
                break;
            }
        }
        
        if (!exists) {
            all_groups[index].gid = sup_groups[i];
            all_groups[index].is_primary = 0;
            all_groups[index].is_effective = (sup_groups[i] == effective_gid);
            all_groups[index].is_supplementary = 1;
            struct group *grp = getgrgid(sup_groups[i]);
            if (grp != NULL) {
                strncpy(all_groups[index].group_name, grp->gr_name, sizeof(all_groups[index].group_name) - 1);
            } else {
                snprintf(all_groups[index].group_name, sizeof(all_groups[index].group_name), "group_%d", sup_groups[i]);
            }
            index++;
        }
    }
    
    total_groups = index;
    
    // 按组 ID 排序
    qsort(all_groups, total_groups, sizeof(group_info_t), compare_gids);
    
    // 显示结果
    printf("\n完整的组权限信息:\n");
    printf("%-8s %-10s %-12s %-12s %-15s %s\n", 
           "序号", "组ID", "主要组", "有效组", "补充组", "组名");
    printf("%-8s %-10s %-12s %-12s %-15s %s\n", 
           "----", "----", "----", "----", "----", "----");
    
    for (int i = 0; i < total_groups; i++) {
        printf("%-8d %-10d %-12s %-12s %-15s %s\n",
               i + 1,
               all_groups[i].gid,
               all_groups[i].is_primary ? "是" : "否",
               all_groups[i].is_effective ? "是" : "否",
               all_groups[i].is_supplementary ? "是" : "否",
               all_groups[i].group_name);
    }
    
    // 统计信息
    printf("\n统计信息:\n");
    printf("  总组数: %d\n", total_groups);
    
    int primary_count = 0, effective_count = 0, supplementary_count = 0;
    for (int i = 0; i < total_groups; i++) {
        if (all_groups[i].is_primary) primary_count++;
        if (all_groups[i].is_effective) effective_count++;
        if (all_groups[i].is_supplementary) supplementary_count++;
    }
    
    printf("  主要组: %d\n", primary_count);
    printf("  有效组: %d\n", effective_count);
    printf("  补充组: %d\n", supplementary_count);
    
    // 特殊权限检查
    printf("\n特殊权限检查:\n");
    int has_root_group = 0;
    int has_admin_group = 0;
    
    for (int i = 0; i < total_groups; i++) {
        if (all_groups[i].gid == 0) {
            has_root_group = 1;
        }
        // 检查常见的管理员组
        if (strcmp(all_groups[i].group_name, "wheel") == 0 ||
            strcmp(all_groups[i].group_name, "sudo") == 0 ||
            strcmp(all_groups[i].group_name, "adm") == 0) {
            has_admin_group = 1;
        }
    }
    
    printf("  Root 组权限: %s\n", has_root_group ? "是" : "否");
    printf("  管理员组权限: %s\n", has_admin_group ? "是" : "否");
    
    // 清理内存
    if (sup_groups) free(sup_groups);
    if (all_groups) free(all_groups);
}

int main() {
    analyze_process_groups();
    return 0;
}

示例4:组权限验证和安全检查

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>

// 检查进程是否属于指定组
int is_process_member_of_group(gid_t target_gid) {
    // 首先检查主要组和有效组
    if (getgid() == target_gid || getegid() == target_gid) {
        return 1;
    }
    
    // 检查补充组
    int sup_count = getgroups(0, NULL);
    if (sup_count <= 0) {
        return 0;
    }
    
    gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
    if (sup_groups == NULL) {
        return 0;
    }
    
    if (getgroups(sup_count, sup_groups) == -1) {
        free(sup_groups);
        return 0;
    }
    
    for (int i = 0; i < sup_count; i++) {
        if (sup_groups[i] == target_gid) {
            free(sup_groups);
            return 1;
        }
    }
    
    free(sup_groups);
    return 0;
}

// 获取特定组的权限级别
typedef enum {
    NO_ACCESS = 0,
    SUPPLEMENTARY_ACCESS = 1,
    PRIMARY_ACCESS = 2,
    EFFECTIVE_ACCESS = 3,
    ROOT_ACCESS = 4
} access_level_t;

access_level_t get_group_access_level(gid_t target_gid) {
    if (target_gid == 0 && (getgid() == 0 || getegid() == 0)) {
        return ROOT_ACCESS;
    }
    
    if (getegid() == target_gid) {
        return EFFECTIVE_ACCESS;
    }
    
    if (getgid() == target_gid) {
        return PRIMARY_ACCESS;
    }
    
    // 检查补充组
    int sup_count = getgroups(0, NULL);
    if (sup_count > 0) {
        gid_t *sup_groups = malloc(sup_count * sizeof(gid_t));
        if (sup_groups != NULL) {
            if (getgroups(sup_count, sup_groups) != -1) {
                for (int i = 0; i < sup_count; i++) {
                    if (sup_groups[i] == target_gid) {
                        free(sup_groups);
                        return SUPPLEMENTARY_ACCESS;
                    }
                }
            }
            free(sup_groups);
        }
    }
    
    return NO_ACCESS;
}

void check_common_groups() {
    printf("=== 常见组权限检查 ===\n");
    
    // 检查一些常见的系统组
    struct {
        const char *name;
        gid_t gid;
    } common_groups[] = {
        {"root", 0},
        {"wheel", 0},  // 需要查找实际 GID
        {"sudo", 0},
        {"adm", 0},
        {"disk", 0}
    };
    
    // 获取这些组的实际 GID
    for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups[0]); i++) {
        struct group *grp = getgrnam(common_groups[i].name);
        if (grp != NULL) {
            common_groups[i].gid = grp->gr_gid;
        }
    }
    
    printf("%-12s %-8s %-15s %s\n", "组名", "组ID", "访问级别", "成员状态");
    printf("%-12s %-8s %-15s %s\n", "----", "----", "----", "----");
    
    for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups[0]); i++) {
        if (common_groups[i].gid != 0) {
            access_level_t level = get_group_access_level(common_groups[i].gid);
            int is_member = is_process_member_of_group(common_groups[i].gid);
            
            const char *level_str;
            switch (level) {
                case ROOT_ACCESS: level_str = "Root"; break;
                case EFFECTIVE_ACCESS: level_str = "Effective"; break;
                case PRIMARY_ACCESS: level_str = "Primary"; break;
                case SUPPLEMENTARY_ACCESS: level_str = "Supplementary"; break;
                default: level_str = "None"; break;
            }
            
            printf("%-12s %-8d %-15s %s\n",
                   common_groups[i].name,
                   common_groups[i].gid,
                   level_str,
                   is_member ? "是" : "否");
        }
    }
}

int main() {
    check_common_groups();
    return 0;
}

9. 实际应用场景

getgroups 在以下场景中非常有用:

场景1:权限验证

int can_access_resource(gid_t required_group) {
    return is_process_member_of_group(required_group);
}

场景2:安全审计

void audit_process_groups() {
    int count = getgroups(0, NULL);
    syslog(LOG_INFO, "进程拥有 %d 个补充组", count);
}

场景3:访问控制

int check_file_group_permission(const char *filename) {
    struct stat file_stat;
    if (stat(filename, &file_stat) == 0) {
        return is_process_member_of_group(file_stat.st_gid);
    }
    return 0;
}

10. 注意事项

使用 getgroups 时需要注意:

  1. 缓冲区大小: 确保缓冲区足够大,避免 EINVAL 错误
  2. 内存管理: 正确分配和释放内存
  3. 错误处理: 检查返回值和 errno
  4. 系统限制: 了解 NGROUPS_MAX 限制
  5. 并发安全: 在多线程环境中注意数据一致性

总结

getgroups 是管理进程组权限的重要函数,关键要点:

  1. 补充组获取: 获取进程除主要组外的所有组
  2. 双重调用: 通常需要先获取数量,再获取实际数据
  3. 权限检查: 是权限验证和访问控制的基础
  4. 安全相关: 在安全审计和权限管理中广泛使用
  5. 系统集成: 与 /etc/group 等系统文件紧密相关

正确使用 getgroups 可以帮助程序准确了解当前的组权限状态,实现更精细的访问控制和安全检查

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注