在讨论这个setuid函数之前,我们首先要了解的一个东西就是内核为每个进程维护的三个UID值。这三个UID分别是实际用户ID(real uid)、有效用户ID(effective uid)、保存的设置用户ID(saved set-user-ID)。

  首先说这个实际用户ID,就是我们当前以哪个用户登录了,我们运行的程序的实际用户ID就是这个用户的ID。有效用户ID就是当前进程是以哪个用户ID来运行的,一般情况下是实际用户ID,如果可执行文件具有了SUID权限,那么它的有效用户ID就是可执行文件的拥有者。保存的设置用户ID就是有效用户ID的一个副本,与SUID权限有关。

  关于那个SUID,最经典的例子莫过于passwd命令。passwd这个可执行文件的所有者是root,但是其他用户对于它也有执行权限,并且它自身具有SUID权限。那么当其他用户来执行passwd这个可执行文件的时候,产生的进程的就是以root用户的ID来运行的。如下图所示:

  

  这个passwd的所有者是root,并且在权限那一栏中s代表的是SUID权限。我以普通用户michael来运行这个命令,产生的进程如下:

  大家可以看到,倒数第二行这个进程其命令是passwd,但是其USER却是root。这就是SUID权限。

  接下来,我们开始扯正题,setuid函数。这是setuid函数的声明和返回值:

  

  如果成功返回0,如果失败返回-1,并将errno设置为合适的值。

  关于这个函数的功能,APUE(《UNIX环境高级编程》,下面的APUE为相同的意思,不再赘述)上面是这样写的:

  

  这样光讲概念可能有点干巴巴了,下面我来举几个分别说明一下:

  开始讲之前,我先验证一下我的电脑是支持"保存的设置用户ID"这个功能的,在电脑上运行以下代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<stdarg.h>
#include<errno.h>
#include<unistd.h>
#define BUFSIZE 512
void err_exit(char *fmt,...);
int main(int argc,char *argv[])
{if(sysconf(_SC_SAVED_IDS) > 0)printf("_POSIX_SAVED_IDS effective!\n");return 0;
}

程序的运行结果如下:

  

  首先说一下我用到的用户,一共使用了三个用户,root(UID=0),michael(UID=500),xiaojie(UID=501)如下图所示:

  

  先讲第一点,如果进程具有超级用户特权,那么这三个UID都被设置成了uid(setuid函数的参数)的值。比如下面这段代码:

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main(int argc,char *argv[])
{uid_t ruid,euid,suid;//这三个uid分别是实际用户ID,有效用户ID,保存的设置用户IDsetuid(500);getresuid(&ruid,&euid,&suid);printf("real:%d\teffective:%d\tset-user-id:%d\n",ruid,euid,suid);return 0;
}

我用root用户来运行这个程序,那么刚开始这个进程的有效用户ID为0,然后这个进程中调用setuid(500),这三个UID的值都会被设置成500(在程序中通过getresuid函数来获取进程的三个UID的值),运行结果如下图:

  

  接下来我们再来讨论第二种情况,如果函数的参数uid等于进程的实际用户ID或者保存的设置用户ID,那么就将有效用户ID设置为uid,其他的两个(实际用户ID和保存的设置用户ID)保持不变。比如下面这个程序:

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
void err_exit(char *fmt,...);
int main(int argc,char *argv[])
{uid_t ruid,euid,suid;printf("Before:\n");if(-1 == getresuid(&ruid,&euid,&suid))err_exit("[getresuid]: ");printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);setuid(500);printf("After:\n");if(-1 == getresuid(&ruid,&euid,&suid))err_exit("[getresuid]: ");printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);return 0;
}

  关于这段代码生成的可执行文件,我让它的拥有者变成了xiaojie(UID=501),并且给它加上了SUID权限,如下图所示:

  

  然后我用michael用户(UID=500)来运行这个程序,那么这个程序的实际用户ID=500,而有效用户ID和保存的设置用户ID都为501。接着,在这个程序中调用setuid函数,传入的参数为500,大家可以猜想到,最终应该只有有效用户ID受到影响,而保存的设置用户ID保持不变。运行结果如下图所示:

  

  这就符合了我们的预期,仅仅只有有效用户ID受到了影响,而实际用户ID和保存的设置用户ID并没有改变。(这里我仅仅是验证了保存的设置用户ID不受影响,大家也可以自己写一个程序,来验证实际用户ID并不受影响。)

  最后一点,如果setuid函数的参数的uid的值即不等于实际用户ID,也不等于保存的设置用户ID,而且这个进程的有效用户ID也不是0(非特权进程)。那么这个程序就会出错,并将errno设置为EPERM。比如下面这个程序:

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main(int argc,char *argv[])
{uid_t ruid,euid,suid;getresuid(&ruid,&euid,&suid);printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);if(-1 == setuid(500)){printf("%s\n",strerror(errno));if(errno == EPERM){printf("Error number is EPERM!\n");}}else{getresuid(&ruid,&euid,&suid);printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);}return 0;
}

  我以xiaojie用户(UID=501)来运行这个程序,那么这个程序的三个用户ID都为501,然后在这个程序中,调用setuid(500)。于是程序就会报错,并将errno设置为EPERM,如下图所示:

  

OK,这就是APUE上关于setuid函数的解释,讲了这么多,综合成一句话就是,root是神,想怎么干就怎么干,没人管得了。而普通用户呢,只能管自己分内的事(把进程的有效ID改成自己的ID),不能胡乱来。

linux c setuid函数解析相关推荐

  1. linux驱动---ioctl函数解析

    转自(39条消息) linux驱动---ioctl函数解析_那可真是太开心了的博客-CSDN博客_linux驱动ioctl 参考:ioctl,unlocked_ioctl 处理方法-阿里云开发者社区 ...

  2. linux网络编程函数解析之——setsockopt / getsockopt用法

    linux网络编程函数解析之--setsockopt / getsockopt用法 工程中无线传输方面的东西用到了setsockopt(),getsockopt().网上相关博客很多,而且类似,原文出 ...

  3. setuid函数解析

    在讨论这个setuid函数之前,我们首先要了解的一个东西就是内核为每个进程维护的三个UID值.这三个UID分别是实际用户ID(real uid).有效用户ID(effective uid).保存的设置 ...

  4. linux之getcwd函数解析,如何在Linux 中使用getcwd()函数

    如何在Linux 中使用getcwd()函数 发布时间:2021-03-15 17:35:21 来源:亿速云 阅读:106 作者:Leah 这期内容当中小编将会给大家带来有关如何在Linux 中使用g ...

  5. linux之getcwd函数解析,Linux 中C语言getcwd()函数的用法

    Linux 中C语言getcwd()函数的用法 先来看该函数的声明: #include char *getcwd(char *buf,size_t size); 介绍: 参数说明:getcwd()会将 ...

  6. linux 声明消息函数,linux消息队列函数--解析

    #include www.2cto.com --------------------------------------------------------------------- 1.系统建立IP ...

  7. linux c strtol 函数解析

    来自于:http://www.cnblogs.com/lzjsky/archive/2011/01/05/1926369.html    感谢作者! long int strtol(const cha ...

  8. linux之creat函数解析

    creat函数完全可以被open函数替代: open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode); [lingyun@localhost create] ...

  9. linux open函数解释,linux之open函数解析

    一. open.c /********************************************************************************* *       ...

最新文章

  1. 独家 | 一文读懂Corda分布式记账技术
  2. python爬虫接单经历_一个Python小白5个小时爬虫经历
  3. c语言中头结点不为零怎么写,C语言不带表头结点的单链表操作
  4. HibernateNONSTRICT_READ_WRITE CacheConcurrencyStrategy如何工作
  5. Activity中 onResume和onPause与onStart()和onStop()的一些思考
  6. mysql与缓存脏读_MySQL 事务的隔离级别问题 之 脏读
  7. 动态规划 所有题型的总结
  8. c语言第六章条件型循环结构,C语言课件(第六章 循环结构)
  9. python编辑程序模型_python并发编程之IO模型
  10. 频发:故障排除之又见 ORA-4031丨云和恩墨技术通讯
  11. NB-IOT(4)---移远NB-IOT BC26模块模组简介和实际应用方向详解
  12. 涨价妥妥的!一加7 Pro欧洲价格曝光:顶配或超6000
  13. java编码gbk的不可_解决 java “错误:编码GBK 的不可映射字符”
  14. usaco-pprime-pass
  15. hadoop面试题汇总
  16. 马斯克、脑机交互与人机融合
  17. 软件工程复试面试问题总结(二)
  18. 小米Pro 安装Ubuntu,以及安装成功之后,各种关机重启的卡死问题
  19. 地图制图基础(三):地图制作要求
  20. java怎么样实现反射_Rust如何实现反射?

热门文章

  1. Python基本语法_基本数据类型_字典类型详解
  2. PCB铜箔厚度、线宽与允许通多电流大小的关系
  3. Altium Designer原理图模板设计
  4. [C编码笔记] 空串与NULL是不一样的
  5. shell中sed命令的用法
  6. Golang面向过程编程-函数
  7. angular-fullstack test
  8. DBA工具——DMV——通过sys.dm_exec_procedure_stats查看存储过程执行信息
  9. Color the ball----HDOJ1556
  10. 设置/修改centos上的swap交换分区的方法