在讨论这个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) > )
printf("_POSIX_SAVED_IDS effective!\n"); return ;
}

  程序的运行结果如下:

  

  首先说一下我用到的用户,一共使用了三个用户,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,保存的设置用户ID setuid();
getresuid(&ruid,&euid,&suid);
printf("real:%d\teffective:%d\tset-user-id:%d\n",ruid,euid,suid); return ;
}

  我用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(- == getresuid(&ruid,&euid,&suid))
err_exit("[getresuid]: ");
printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid);
setuid();
printf("After:\n");
if(- == getresuid(&ruid,&euid,&suid))
err_exit("[getresuid]: ");
printf("real:%d\teffective:%d\tset-user:%d\n",ruid,euid,suid); return ;
}

  关于这段代码生成的可执行文件,我让它的拥有者变成了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(- == setuid())
{
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 ;
}

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

  

  

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

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

  

setuid函数解析的更多相关文章

  1. [转]javascript eval函数解析json数据时为什加上圆括号eval("("+data+")")

    javascript eval函数解析json数据时为什么 加上圆括号?为什么要 eval这里要添加 “("("+data+")");//”呢?   原因在于: ...

  2. PHP json_decode 函数解析 json 结果为 NULL 的解决方法

    在做网站 CMS 模块时,对于模块内容 content 字段,保存的是 json 格式的字符串,所以在后台进行模块内容的编辑操作 ( 取出保存的数据 ) 时,需要用到 json_decode() 函数 ...

  3. Matlab中bsxfun和unique函数解析

    一.问题来源 来自于一份LSH代码,记录下来. 二.函数解析 2.1 bsxfun bsxfun是一个matlab自版本R2007a来就提供的一个函数,作用是”applies an element-b ...

  4. socket使用TCP协议时,send、recv函数解析以及TCP连接关闭的问题

    Tcp协议本身是可靠的,并不等于应用程序用tcp发送数据就一定是可靠的.不管是否阻塞,send发送的大小,并不代表对端recv到多少的数据. 在阻塞模式下, send函数的过程是将应用程序请求发送的数 ...

  5. sigaction函数解析

    http://blog.chinaunix.net/uid-1877180-id-3011232.html sigaction函数解析  sigaction函数的功能是检查或修改与指定信号相关联的处理 ...

  6. driver_register()函数解析

    driver_register()函数解析 /** * driver_register - register driver with bus * @drv: driver to register *  ...

  7. async函数解析

    转载请注明出处:async函数解析 async函数是基于Generator函数实现的,也就是说是Generator函数的语法糖.在之前的文章有介绍过Generator函数语法和异步应用,如果对其不了解 ...

  8. tf.train.shuffle_batch函数解析

    tf.train.shuffle_batch (tensor_list, batch_size, capacity, min_after_dequeue, num_threads=1, seed=No ...

  9. oracle中next_day()、last_day()函数解析

    oracle中next_day()函数解析 Sql代码 当前系统时间的下一星期一的时间select   next_day(sysdate,1) from dual NEXT_DAY(date,char ...

随机推荐

  1. WPF 之 鼠标双击事件

    由于WPF中没有鼠标的双击事件,因而只能通过MouseDown事件来模拟.当连续的两次MouseDown事件的时间间隔,没有超过一个设定的时间阈值时,就计算为一个双击事件,并作相应的处理. 利用WPF ...

  2. 使用solr的函数查询,并获取tf*idf值

    1. 使用函数df(field,keyword) 和idf(field,keyword). http://118.85.207.11:11100/solr/mobile/select?q={!func ...

  3. cocos2dx3.0-tinyxml在Android环境下解析xml失败的问题

    本文由@呆代待殆原创,转载请注明出处. 正常情况下,我们在用tinyxml读取xml文件的的时候,会像下面这样写. std::string filePath = FileUtils::getInsta ...

  4. 沈逸老师PHP魔鬼特训笔记(10)

    为了防止代码让我们混淆不清,大家看视频中的分离方法 1.新建了一个文件夹叫code (这代表是代码) 2.再新建一个文件夹叫page (这代表是页面) 代码该怎么写呢? 这里要记住口诀 1.index ...

  5. OC之protocol监听器的实现

    画图 图解 代码 总结 一.画图 本人画了一个图(字体和画图水平请忽略) 二.图解 1.首先我们上边是一个按钮的类,按钮当中包括了一条线,这个线是什么呢? 其实难理解就难理解到这条线上了 1⃣️这条线 ...

  6. Java学习笔记——Java工厂模式之简单工厂

    package com.app; import java.util.Date; /* * 工厂模式:简单工厂.工厂方法.抽象工厂 * * */ public class Test0718_Factor ...

  7. linux服务监控脚本

    配置需要监控的服务器 数组定义:host_ports=(host_name=host_port=uri_path)host_name为容易识别的服务器名称host_port为服务器ip和服务端口uri ...

  8. Linux的SOCKET编程详解

    1. 网络中进程之间如何通信 进 程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如 U ...

  9. first集合及follow集合

    前面那片文章生成的语法分析表并不是最优的,因为有些项在遇到错误输入的时候,并不是采取报错,而是执行规约,直到不能再规约的时候才报错.这是不科学的,我们需要在得到错误输入的时候立马报错,为了实现这个功能 ...

  10. [转]如何制作tizen镜像文件(图文教程)?

    http://blog.csdn.net/flydream0/article/details/9163119 之前已讲解了如何下载及编译tizen源码(http://blog.csdn.net/fly ...