getgroups系统调用及示例

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

1. 函数介绍

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

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

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

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

2. 函数原型

1
2
3
4
5
#include <unistd.h>
#include <sys/types.h>

int getgroups(int size, gid_t list&#91;]);

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 错误码

  • EINVAL: size 参数小于补充组的实际数量(缓冲区不足)

  • EFAULT: list 指针指向无效内存地址

  • EPERM: 在某些安全限制下可能返回(较少见)

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

  • setgroups(): 设置进程的补充组列表

  • initgroups(): 根据用户信息初始化补充组列表

  • getgid(): 获取进程的真实组 ID

  • getegid(): 获取进程的有效组 ID

  • getuid(), geteuid(): 获取用户 ID 相关函数

  • setgid(), setegid(): 设置组 ID

  • getgrouplist(): 获取用户的所有组(包括主要组)

  • /etc/group: 系统组信息文件

  • /etc/passwd: 用户主要组信息文件

8. 示例代码

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#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&#91;i]);

// 尝试获取组名
struct group *grp = getgrgid(group_list&#91;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&#91;i] == primary_gid) {
found_in_supplementary = 1;
break;
}
}

printf("主要组是否在补充组中: %s\n",
found_in_supplementary ? "是" : "否");

free(group_list);
return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#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&#91;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:完整的组权限分析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#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&#91;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&#91;index].gid = primary_gid;
all_groups&#91;index].is_primary = 1;
all_groups&#91;index].is_effective = (primary_gid == effective_gid);
all_groups&#91;index].is_supplementary = 0;
struct group *grp = getgrgid(primary_gid);
if (grp != NULL) {
strncpy(all_groups&#91;index].group_name, grp->gr_name, sizeof(all_groups&#91;index].group_name) - 1);
} else {
snprintf(all_groups&#91;index].group_name, sizeof(all_groups&#91;index].group_name), "group_%d", primary_gid);
}
index++;

// 添加有效组(如果不同于主要组)
if (effective_gid != primary_gid) {
all_groups&#91;index].gid = effective_gid;
all_groups&#91;index].is_primary = 0;
all_groups&#91;index].is_effective = 1;
all_groups&#91;index].is_supplementary = 0;
struct group *grp = getgrgid(effective_gid);
if (grp != NULL) {
strncpy(all_groups&#91;index].group_name, grp->gr_name, sizeof(all_groups&#91;index].group_name) - 1);
} else {
snprintf(all_groups&#91;index].group_name, sizeof(all_groups&#91;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&#91;j].gid == sup_groups&#91;i]) {
all_groups&#91;j].is_supplementary = 1;
exists = 1;
break;
}
}

if (!exists) {
all_groups&#91;index].gid = sup_groups&#91;i];
all_groups&#91;index].is_primary = 0;
all_groups&#91;index].is_effective = (sup_groups&#91;i] == effective_gid);
all_groups&#91;index].is_supplementary = 1;
struct group *grp = getgrgid(sup_groups&#91;i]);
if (grp != NULL) {
strncpy(all_groups&#91;index].group_name, grp->gr_name, sizeof(all_groups&#91;index].group_name) - 1);
} else {
snprintf(all_groups&#91;index].group_name, sizeof(all_groups&#91;index].group_name), "group_%d", sup_groups&#91;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&#91;i].gid,
all_groups&#91;i].is_primary ? "是" : "否",
all_groups&#91;i].is_effective ? "是" : "否",
all_groups&#91;i].is_supplementary ? "是" : "否",
all_groups&#91;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&#91;i].is_primary) primary_count++;
if (all_groups&#91;i].is_effective) effective_count++;
if (all_groups&#91;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&#91;i].gid == 0) {
has_root_group = 1;
}
// 检查常见的管理员组
if (strcmp(all_groups&#91;i].group_name, "wheel") == 0 ||
strcmp(all_groups&#91;i].group_name, "sudo") == 0 ||
strcmp(all_groups&#91;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:组权限验证和安全检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#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&#91;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&#91;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&#91;] = {
{"root", 0},
{"wheel", 0}, // 需要查找实际 GID
{"sudo", 0},
{"adm", 0},
{"disk", 0}
};

// 获取这些组的实际 GID
for (int i = 0; i < sizeof(common_groups)/sizeof(common_groups&#91;0]); i++) {
struct group *grp = getgrnam(common_groups&#91;i].name);
if (grp != NULL) {
common_groups&#91;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&#91;0]); i++) {
if (common_groups&#91;i].gid != 0) {
access_level_t level = get_group_access_level(common_groups&#91;i].gid);
int is_member = is_process_member_of_group(common_groups&#91;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&#91;i].name,
common_groups&#91;i].gid,
level_str,
is_member ? "是" : "否");
}
}
}

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

9. 实际应用场景

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

场景1:权限验证

1
2
3
4
int can_access_resource(gid_t required_group) {
return is_process_member_of_group(required_group);
}

场景2:安全审计

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

场景3:访问控制

1
2
3
4
5
6
7
8
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 时需要注意:

缓冲区大小: 确保缓冲区足够大,避免 EINVAL 错误

内存管理: 正确分配和释放内存

错误处理: 检查返回值和 errno

系统限制: 了解 NGROUPS_MAX 限制

并发安全: 在多线程环境中注意数据一致性

总结

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

补充组获取: 获取进程除主要组外的所有组

双重调用: 通常需要先获取数量,再获取实际数据

权限检查: 是权限验证和访问控制的基础

安全相关: 在安全审计和权限管理中广泛使用

系统集成: 与 /etc/group 等系统文件紧密相关

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

data-ad-format="auto" data-full-width-responsive="true">