浅析open函数O_CLOEXEC模式和fcntl函数FD_CLOEXEC选项

man open里有这么一个flag:

O_CLOEXEC (Since Linux 2.6.23)

  意思就是新的内核里的这个选项是把fcntl的这个设置放在open里原子操作,以免在多线程程序里有可能会出现fcntl在设置的同时其它线程在fork+execve,虽然在线程里fork比较罕见.这个选项的意思就是子进程默认是继承父进程打开的所有fd,如果句柄加入了这个设置,在execve替换进程时就会关闭设置这个选项的所有fd.当调用exec()函数成功后,文件描述符会自动关闭。在以往的内核版本(2.6.23以前)中,需要调用
fcntl(fd, F_SETFD, FD_CLOEXEC) 来设置这个属性。而
新版本(2.6.23开始)中,可以在调用open函数的时候,通过 flags 参数设置 CLOEXEC 功能,如 open(filename, O_CLOEXEC)。

      虽然新版本支持在open时设置CLOEXEC,但是在编译的时候还是会提示错误 - error: ‘O_CLOEXEC’ undeclared (first use in this function)。原来这个新功能要求我们手动去打开,需要设置一个宏(_GNU_SOURCE)。可通过以下两种方法来设置这个宏以打开新功能:

1. 在源代码中加入 #define _GNU_SOURCE
2. 在编译参数中加入 -D_GNU_SOURCE


关于open函数O_CLOEXEC模式,fcntl函数FD_CLOEXEC选项,总结为如下几点:


1.调用open函数O_CLOEXEC 模式 打开的文件描述符在执行exec调用新程序中关闭,且为原子操作。

2.调用 open函数不使用O_CLOEXEC模式打开的文件描述符,然后调用fcntl 函数设置FD_CLOEXEC选项,效果和使用O_CLOEXEC选项open函数相同,但分别调用open、fcnt两个l函数,不是原子操作,多线程环境中存在竞态条件,故用open函数O_CLOEXEC选项代替之可能的竞态场景:线程甲打开一文件描述符,尝试用fcntl设置FD_CLOEXEC,于此同时,线程乙执行fork()调用,然后执行exec()执行任意一个程序,假设在线程甲调用open和fcntl之间,线程乙完成了fork和exec操作,就会导致无意间将打开的文件描述符泄露给不安全的程序。注意,FD_CLOEXEC标志为进程和文件描述符私有,对这一标志的修改不会影响同一进程或者不同进程的其他文件描述符。

3.
调用open函数O_CLOEXEC模式打开的文件描述符,或是使用fcntl设置FD_CLOEXEC选项,这二者得到(处理)的描述符在通过fork调用产生的子进程中均不被关闭。

4.调用dup族类函数得到的新文件描述符将清除O_CLOEXEC模式。


意义:在进程执行exec系统调用时关闭此打开的文件描述符。防止父进程泄露打开的文件给子进程,即便子进程没有相应权限。

fd泄露引起普通用户访问无权限的文件:

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <sys/types.h>  
  4. #include <fcntl.h>  
  5. #include <stdlib.h>  
  6.   
  7. int main()  
  8. {  
  9.     int count;  
  10.     pid_t pid;  
  11.     char buf[1024] = {0};  
  12.     int fd;  
  13.   
  14.     //以root身份打开文件  
  15.     fd = open("/etc/shadow", O_RDONLY);  
  16.     if( fd < 0 )  
  17.     {  
  18.         perror("open /etc/shadow");  
  19.         return -1;  
  20.     }  
  21.   
  22.     pid = fork();  
  23.     if( 0 == pid )  
  24.     {  
  25.         seteuid(500);  
  26.         setegid(500);  
  27.         //fd是从父进程泄露出来,不受权限制约,可以读取文件  
  28.         printf("EUID:%d, UID:%d\n", geteuid(), getuid());  
  29.         while( (count = read(fd, buf, sizeof(buf))) > 0 )  
  30.         {  
  31.             write(STDOUT_FILENO, buf, count);  
  32.         }  
  33.           
  34.         close(fd);  
  35.   
  36.         //普通用户! 无法打开文件  
  37.         fd = open("/etc/shadow", O_RDONLY);  
  38.         printf("fd=%d\n", fd);  
  39.   
  40.         if( fd<0 )  
  41.         {              
  42.             perror("open /etc/shadow");  
  43.             return -1;  
  44.         }  
  45.         return 0;  
  46.     }  
  47.   
  48.     close(fd);  
  49.     return 0;  
  50. }  


设置O_CLOEXEC一般是在open时设置,这个是原子操作;也可以用fcntl()的F_SETFD命令来设置,但它有并发危险,如多线程中,一个线程将要设置O_CLOEXEC标志时,虽一个线程fork(),且先得到执行,导致打开的文件描述符泄露到子进程中。


http://blog.csdn.net/konga/article/details/39062691

http://blog.chinaunix.net/uid-24907956-id-3969651.html


  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值