setgid系统调用及示例

我们来深入学习 setgid 系统调用,在 Linux 系统中,每个进程都运行在一个特定的用户(User)和组(Group)上下文中。这个上下文决定了进程拥有哪些权限,比如能否读写某个文件、能否绑定到特权端口(端口号小于 1024)等。

setgid系统调用及示例-CSDN博客

setfsgid系统调用及示例

系统调用LinuxGuide

setpgid系统调用及示例_setpgid函数作用-CSDN博客

1. 函数介绍

在 Linux 系统中,每个进程都运行在一个特定的用户(User)和组(Group)上下文中。这个上下文决定了进程拥有哪些权限,比如能否读写某个文件、能否绑定到特权端口(端口号小于 1024)等。

每个进程通常有三类相关的用户/组 ID:

  • 真实 ID (Real ID): 登录系统时分配给用户的 ID。它标识了“你是谁”。
  • 有效 ID (Effective ID): 内核用来进行权限检查时使用的 ID。它决定了“你能做什么”。
  • 保存的设置 ID (Saved Set-ID): 用于在有效 ID 和真实 ID 之间来回切换的一个“备份”ID。

[setgid(Set Group ID)系统调用的主要作用是设置调用进程的有效组 ID (Effective GID)。根据调用者的权限和当前 ID 状态,它也可能同时修改保存的设置组 ID (Saved Set-GID)。

简单来说,setgid 让你的程序可以“以某个组的身份”去执行操作,从而获得或限制与该组相关的权限。](https://www.calcguide.tech/2025/08/23/setgid%e7%b3%bb%e7%bb%9f%e8%b0%83%e7%94%a8%e5%8f%8a%e7%a4%ba%e4%be%8b/)

一个常见的场景是:一个需要访问特定组才能读写的文件或设备的程序,可以通过 setgid 来获取相应的组权限。

2. 函数原型

#include <unistd.h> // 包含系统调用声明
#include <sys/types.h> // 包含 gid_t 类型定义

int setgid(gid_t gid);

3. 功能

设置调用进程的有效组 ID (Effective GID)。根据调用者的权限(是否为 root)和目标 gid,行为会有所不同。

4. 参数

  • gid:
    • gid_t 类型。
    • 指定要设置的新的有效组 ID。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。

6. 行为规则

setgid 的具体行为取决于调用进程的权限:

  1. 如果调用者是特权用户 (超级用户, root):
    • 可以将有效 GID (egid) 设置为任意有效的组 ID。
    • 同时,真实 GID (rgid) 和保存的设置 GID (sgid) 也会被设置为相同的 gid 值。
    • 这是特权用户的强大能力。
  2. 如果调用者是普通用户:
    • gid 参数必须是调用进程的真实 GID (rgid) 或 保存的设置 GID (sgid) 之一。
    • 只能将有效 GID (egid) 设置为 rgid 或 sgid
    • 真实 GID (rgid) 和 保存的设置 GID (sgid) 不会被修改
    • 这是为了防止普通用户随意获取其他组的权限。

7. 错误码 (errno)

  • EINVALgid 参数无效(虽然在 Linux 中通常不会返回此错误)。
  • EPERM: 调用者没有权限执行此操作。对于普通用户,这意味着 gid 既不是 rgid 也不是 sgid

8. 相似函数或关联函数

  • setuid: 设置用户 ID,与 setgid 功能类似,但针对的是用户而非组。
  • setegid: 专门用于设置有效组 ID,行为比 setgid 更受限(普通用户只能设置为 rgid 或 sgid)。
  • setregid: 同时设置真实组 ID 和 有效组 ID。
  • setresgid: 同时设置 真实有效 和 保存的设置 组 ID,提供了最精细的控制。
  • getgid: 获取调用进程的真实组 ID。
  • getegid: 获取调用进程的有效组 ID。
  • getgroups: 获取调用进程所属的附加组列表。

9. 示例代码

下面的示例演示了 setgid 在不同权限下的行为,以及如何检查组 ID。

#define _GNU_SOURCE // 启用 GNU 扩展以使用 getresgid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

void print_current_gids(const char* context) {
    gid_t rgid, egid, sgid;
    printf("[%s] Current GIDs - Real: %d, Effective: %d, Saved: %d\n",
           context, getgid(), getegid(), (getresgid(&rgid, &egid, &sgid) == 0) ? sgid : -1);
}

int main() {
    gid_t original_rgid, original_egid, original_sgid;
    gid_t target_gid;
    int result;

    printf("--- Demonstrating setgid ---\n");

    // 1. 获取并打印初始 GID
    if (getresgid(&original_rgid, &original_egid, &original_sgid) != 0) {
        perror("getresgid");
        exit(EXIT_FAILURE);
    }
    print_current_gids("Start");

    // 2. 检查是否以 root 权限运行
    if (geteuid() == 0) {
        printf("\nRunning as ROOT (Privileged User)\n");
        // 作为 root,可以设置为任意有效的 GID
        // 这里我们尝试设置为 'daemon' 组 (通常 GID 1, 但请检查你的系统)
        target_gid = 1;
        printf("Attempting to set GID to %d (usually 'daemon' group)...\n", target_gid);

        print_current_gids("Before setgid");
        result = setgid(target_gid);
        if (result == 0) {
            printf("setgid(%d) succeeded.\n", target_gid);
            print_current_gids("After setgid");
            printf("Note: All GIDs (Real, Effective, Saved) are now %d.\n", target_gid);
        } else {
            perror("setgid");
            printf("Failed to set GID to %d.\n", target_gid);
        }

    } else {
        printf("\nRunning as a REGULAR USER (UID: %d)\n", getuid());

        // 作为普通用户,只能设置为自己的 rgid 或 sgid
        target_gid = original_rgid; // 选择设置为自己的真实 GID (这不会改变任何东西)
        printf("Attempting to set GID to my Real GID (%d)...\n", target_gid);
        print_current_gids("Before setgid");
        result = setgid(target_gid);
        if (result == 0) {
            printf("setgid(%d) succeeded (as expected).\n", target_gid);
            print_current_gids("After setgid");
        } else {
            perror("setgid");
        }

        // 尝试设置为一个无效的 GID (比如一个不存在的或不属于我的 GID)
        // 这通常会失败
        target_gid = 9999; // 假设这是一个无效的或不属于当前用户的 GID
        printf("\nAttempting to set GID to an invalid/different GID (%d)...\n", target_gid);
        result = setgid(target_gid);
        if (result == -1) {
            if (errno == EPERM) {
                printf("setgid(%d) failed with EPERM (Operation not permitted) - as expected for a regular user.\n", target_gid);
                printf("This is because %d is not my Real GID (%d) or Saved Set-GID (%d).\n",
                       target_gid, original_rgid, original_sgid);
            } else {
                perror("setgid");
            }
            print_current_gids("After failed setgid");
        } else {
            printf("setgid(%d) unexpectedly succeeded.\n", target_gid);
            print_current_gids("After unexpected setgid");
        }
    }

    printf("\n--- Summary ---\n");
    printf("The setgid() function changes the Effective GID of the process.\n");
    printf("For root: It can change to any GID, and also changes Real and Saved GID.\n");
    printf("For regular users: It can only change Effective GID to Real or Saved GID.\n");

    return 0;
}

编译和运行:

# 假设代码保存在 setgid_example.c 中
gcc -o setgid_example setgid_example.c

# 1. 作为普通用户运行
./setgid_example

# 2. 作为 root 用户运行 (需要 sudo 权限)
# 注意:切换到 root 权限执行程序有风险,请小心!
sudo ./setgid_example

预期输出 (作为普通用户运行):

--- Demonstrating setgid ---
[Start] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000

Running as a REGULAR USER (UID: 1000)
Attempting to set GID to my Real GID (1000)...
[Before setgid] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000
setgid(1000) succeeded (as expected).
[After setgid] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000

Attempting to set GID to an invalid/different GID (9999)...
setgid(9999) failed with EPERM (Operation not permitted) - as expected for a regular user.
This is because 9999 is not my Real GID (1000) or Saved Set-GID (1000).
[After failed setgid] Current GIDs - Real: 1000, Effective: 1000, Saved: 1000

--- Summary ---
The setgid() function changes the Effective GID of the process.
For root: It can change to any GID, and also changes Real and Saved GID.
For regular users: It can only change Effective GID to Real or Saved GID.

预期输出 (使用 sudo 以 root 权限运行):

--- Demonstrating setgid ---
[Start] Current GIDs - Real: 0, Effective: 0, Saved: 0

Running as ROOT (Privileged User)
Attempting to set GID to 1 (usually 'daemon' group)...
[Before setgid] Current GIDs - Real: 0, Effective: 0, Saved: 0
setgid(1) succeeded.
[After setgid] Current GIDs - Real: 1, Effective: 1, Saved: 1
Note: All GIDs (Real, Effective, Saved) are now 1.

总结:
setgid 是一个基础且重要的系统调用,用于管理进程的组权限。理解它的行为规则(尤其是特权用户和普通用户的区别)对于编写安全的 Linux 程序至关重要。在实际应用中,它常用于守护进程(daemons)或需要特定组权限的程序中。

setgid系统调用及示例-CSDN博客

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

发表回复

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