有时候为了系统安全,会将程序进行降权,但是当需要访问当前不允许访问的资源时,如何处理呢?那就是更改自己的用户ID或组ID,使新的ID具有合适的特权或访问权限,当处理完之后,再降低其特权,下面来介绍一下,如何在程序中动态修改程序的用户ID或组ID。

进程用户ID

首先,我们需要了解,一个进程相关联的ID有多少个,下面表格介绍:

ID

作用

实际用户ID

我们实际上是谁

实际组ID

我们实际上是谁

有效用户ID

用于文件访问权限检查

有效组ID

用于文件访问权限检查

附属组ID

用于文件访问权限检查

保存的设置用户ID

由exec()函数保存

保存的设置组ID

由exec()函数保存

另外,我们可以通过 getuid() 和 geteuid() 函数分别或者实际用户ID和有效用户ID,而设置用户ID我们是没有函数可以直接获取的。

动态更换用户ID

当我们在root用户下通过 chmod +s YourExe 命令给我们的可执行程序设置SUID位(“u+s”设置文件的用户ID位,“g+s”设置组ID位),这个时候,我们可以通过seteuid()函数暂时性的获得root权限。
“+s”设置的标志位含义是当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID

测试代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/syscall.h>

int main(int argc, char *argv[])
{
	int fd;
	unsigned char *dev_name = "/dev/input/event0";

	printf("uid = %d, gid = %d euid = %d\n", getuid(), getgid(), geteuid());
	fd = open(dev_name, O_RDONLY);
	if (fd < 0)
		printf("open %s error,errno %d\n", dev_name, errno);
	else {
		printf("open %s ok\n", dev_name);
		close(fd);
	}

	/* 参数0是root用户ID */
	seteuid(0);
	printf("uid = %d, gid = %d euid = %d\n", getuid(), getgid(), geteuid());
	fd = open(dev_name, O_RDONLY);
	if (fd < 0)
		printf("open %s error,errno %d\n", dev_name, errno);
	else {
		printf("open %s ok\n", dev_name);
		close(fd);
	}

	/* 参数2是当前实际用户ID */
	seteuid(2);
	printf("uid = %d, gid = %d euid = %d\n", getuid(), getgid(), geteuid());
	fd = open(dev_name, O_RDONLY);
	if (fd < 0)
		printf("open %s error,errno %d\n", dev_name, errno);
	else {
		printf("open %s ok\n", dev_name);
		close(fd);
	}
}

默认的,我们user是没有 /dev 目录下节点的读取权限的,当我们去打开该节点时,将会出现报错说没有权限,但是,这个时候,如果通过 seteuid() 函数将有效用户ID设置为 root 用户(ID = 0),则可以正常访问 /dev 目录下的节点,当访问结束后,再通过 seteuid() 函数还原即可,注意,是 seteuid() 函数而不是 setuid() 函数。

实际上,上面示例在user用户环境的的工作步骤如下:

  1. 程序文件由root用户拥有且在设置用户ID位已设置,在user用户空间运行的,所以用户ID信息如下:

实际用户ID = 我们的用户ID
有效用户ID = 我们的用户ID
保存的设置用户ID = root

  1. 所以第一次open /dev 节点将会出现没有权限。
  2. 当调用 seteuid(0) 之后,由于我们已经设置了用户ID位,所以这种行为是允许的,用户ID信息将变为以下:

实际用户ID = 我们的用户ID(未改变)
有效用户ID = root
保存的设置用户ID = root(未改变)

  1. 由于当前有效用户ID为root,所以可以正常访问 /dev 节点。
  2. 当调用 seteuid(2) 降权之后,用户ID信息将变为以下:

实际用户ID = 我们的用户ID(未改变)
有效用户ID = 我们的用户ID
保存的设置用户ID = root(未改变)

  1. 降权后访问 /dev 将会无权限。

通过上述的配置,我们可以动态修改用户 ID,但是程序得到了额外的权限,编写这种程序时谨慎处理。