在讨论这个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),不能胡乱来。

  OK,that‘s all。希望这篇文章对大家能够有帮助。

附上作者原文地址:http://www.cnblogs.com/bwangel23/archive/2015/01/15/4225818.html

setuid函数解析相关推荐

  1. linux c setuid函数解析

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

  2. linux setuid函数_setuid函数解析

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

  3. oracle rpad mysql_Oracle生成不重复票号与LPAD,RPAD与NEXTVAL函数解析

    SELECT TO_CHAR(SYSDATE,'YYMMDD')||LPAD(REFUNDSEQ.NEXTVAL,6,'0') AS RES_ORDER_NO FROM DUAL 该语句拼接 时间 与 ...

  4. json数据解析_VBA 实践指南 -- Split函数解析Json数据

    什么是JSON? JSON(JavaScript Object Notation) 是一种及其轻量级的数据交换格式,它是 ECMAScript (欧洲计算机协会制定的JavaScript规范)的一个子 ...

  5. C/C++ sizeof函数解析——解决sizeof求结构体大小的问题

    C/C++ sizeof函数解析--解决sizeof求结构体大小的问题 参考文章: (1)C/C++ sizeof函数解析--解决sizeof求结构体大小的问题 (2)https://www.cnbl ...

  6. 【Socket网络编程】12. send()、recv()、sendto() 和 recvfrom() 函数解析

    send().recv().sendto()和recvfrom()函数解析 一般情况下,send().recv()在TCP协议下使用,sendto().recvfrom()在UDP协议下使用,也可以在 ...

  7. main() 函数解析(一)——Linux-0.11 剖析笔记(六)

    文章目录 1. 宏定义`_syscall0` 2. `setup.s`读取的参数 3. 读取CMOS实时时钟信息 3.1 `outb_p(value,port)` 3.2 `inb_p(port)` ...

  8. main 函数解析(二)—— Linux-0.11 学习笔记(六)

    main函数解析(二)--Linux-0.11 学习笔记(六) 4.6 blk_dev_init函数 void blk_dev_init(void) {int i;for (i=0 ; i<NR ...

  9. main函数解析(一)——Linux-0.11 学习笔记(五)

    main()函数解析(一)--Linux-0.11 学习笔记(五) 经过了前面的各种铺垫,终于来到了main函数.这篇博客的任务是把init/main.c讲清楚.由于牵扯到很多的函数调用,要想一次就说 ...

最新文章

  1. 检查点重做检查点队列简单总结Strut2教程-java教程
  2. 用rvest包来抓取Google学术搜索数据
  3. 计算机注册表管理,学会注册表几个常用的设置,更好地管理自己的电脑!
  4. cnn图像二分类 python_人工智能Keras图像分类器(CNN卷积神经网络的图片识别篇)...
  5. 文献阅读课10-Neural Relation Extraction for Knowledge Base Enrichment(提取+嵌入+消歧+规范化联合模型,实体已知,仅关系抽取,多词实体)
  6. 时序数据库技术体系 – InfluxDB TSM存储引擎之数据写入
  7. 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
  8. 华为王军:未来座舱操作系统只有1个或者2个生态
  9. 数据结构与算法:十大排序算法之堆排序
  10. [Usaco2007 Demo]City skyline
  11. 唐宇迪ocr检测图片
  12. c语言 段错误 linux
  13. python数字雨_用Python实现黑客帝国代码雨效果(3种方式)
  14. 自抗扰控制(ADRC)—— 扩展状态观测器
  15. android手机连接电脑,安卓手机连接电脑的方法【图文教程】
  16. uniapp上拉加载
  17. ArcGIS基础:面数据空洞填充的方法
  18. 又是白嫖Gitee的一天,PicGo+Gitee搭建图床,用过的都说真香!!!
  19. 光驱读盘能力差的解决方法
  20. veracrypt加密mysql_VeraCrypt怎么用 VeraCrypt加密文件的图文教程方法

热门文章

  1. 正版授权WiFi大师4.0.5 专业版流量主小程序源码部署教程
  2. win2012 r2/win2016修改域密码复杂度策略
  3. 《指数基金投资指南》银行螺丝钉 ---读书
  4. 关于多个债权人申请执行同一被执行人的清偿顺序问题
  5. 操作系统P/V操作(V操作中的典型理解偏差)
  6. 全国计算机等级考试-三级信息安全考试知识点(无顺序)
  7. 软件加壳的简易实现方式
  8. 将视频文件旋转90°的方法
  9. 英语听力采用计算机化考试,高考英语听力机考特点与应对建议
  10. 7.5 《丰田模式》阅读笔记和感悟