Unix中access和open函数的区别

《Unix环境高级编程》学习笔记


学习内容:

在书中图4-8提供了access函数运行实例,自己加了些简单的错误检查机制。程序如下:

#include"apue.h"
#include<fcntl.h>//O_RDONLY

int main(int argc, char* argv[])
{
	if(argc!=2)
		err_quit("usage: ./a.out filename\n");
		
	if(access(argv[1],F_OK)<0)
	{
		err_quit("The file doesn't exist.\n");
	}
	else
	{
		if(access(argv[1],R_OK)<0)//测试读权限
			err_ret("access error for %s",argv[1]);
		else
			printf("read access OK.\n");
		if(access(argv[1],W_OK)<0)//测试写权限
			err_ret("access error for %s",argv[1]);
		else
			printf("write access OK.\n");
		if(access(argv[1],X_OK)<0)//测试执行权限
			err_ret("access error for %s",argv[1]);
		else
			printf("execute access OK.\n");
		
			
		if(open(argv[1],O_RDONLY)<0)//只读打开
			err_ret("open %s error.",argv[1]);
		else
			printf("open for reading %s OK.\n",argv[1]);
	}
	
	exit(0);
}

该程序主要对比了access和open函数的功能。
access函数可以用来测试 real usr ID (实际用户ID)对某些文件是否具有访问权限(对应int mode的常量参数有R_OK,W_OK,X_OK,F_OK);而open函数的返回结果可以测试用户进程的 effective usr ID (有效用户ID)对某些文件是否有访问权限(对应int oflag的的参数有O_RDONLY,O_WRONLY,O_RWONLY等)。


实际测试:

接下来我们在终端输入命令查看程序运行结果。首先查看此可执行文件的详细信息:

$ ls -l a.out
-rwxrwxr-x 1 sar           15945 Nov 30 12:10 a.out

其中第一位-表示文件,后九位是a.out文件的权限位,依次对应三种身份所拥有的权限,身份顺序为:owner、group、others,权限顺序为:readable、writable、executable。此文件的FUID(程序文件的owner ID)为sar,即程序的拥有者是sar,sar对应的权限有readable,writable和executable(9位文件权限符中的前三位)。

再测试可执行文件的权限:

$ ./a.out a.out
read access OK.
write access OK.
execute access OK.
open for reading OK.

执行./a.out a.out命令时,shell会fork()出一个子进程,该进程是 执行a.out(可执行文件)的进程,进程创建者是登录用户。此时该进程的有效用户ID还是这个实际用户的ID(登陆系统的普通用户ID,通常情况下有效用户ID和实际用户ID是相同的)。而且a.out文件也是该普通用户创建的,那么用open函数读a.out文件时,由于该进程的有效用户ID和a.out文件拥有者ID相同,该进程就得到了“rwx”的权限,从输出结果中最后一行可以得到验证。注:有效用户ID是针对进程的概念,不是针对文件的概念。
access函数用来测试实际用户ID能否得到相应访问权限。此时的实际用户ID和有效用户ID相同,那么也同时拥有了“rwx”的权限,从前三行的结果中可以看到。


$ ls -l /etc/shadow
-r-------- 1 root               1315 Jul 17 2002 /etc/shadow

在Linux系统中,/etc/shadow文件通常用来存放用户密码。可以看到文件拥有者是root,其权限为可读,其他用户没有任何权限。
再通过用户创建的进程 测试 /etc/shadow文件 权限:

$ ./a.out /etc/shadow
access error for /etc/shadow: Permission denied
access error for /etc/shadow: Permission denied
access error for /etc/shadow: Permission denied
open /etc/shadow error.: Permission denied

由于实际用户ID和有效用户ID相同,而/etc/shadow文件的所有者是root,即实际用户ID和有效用户ID都和文件拥有者ID不匹配,那么就不会得到“r”权限,无论是open还是access函数都不能正常访问。
接下来我们修改a.out文件的拥有者,再测试access和open函数:

$ sudo su
Password:
$ chown root a.out
$ ls -l a.out
-rwxrwxr-x 1 root           15945 Nov 30 12:10 a.out

可以看到文件拥有者变成了root,但此时所属群组还是sar(书中没有写)。
此时再测试a.out可执行文件:

$ ./a.out a.out
read access OK.
write access OK.
execute access OK.
open for reading a.out OK.

按理说文件拥有者ID已经和 进程(执行a.out可执行文件的进程)的有效用户ID、实际用户ID不匹配,为什么该进程还能对a.out文件有相应的访问权限呢?这就涉及到权限匹配的顺序问题,有点类似短路原则: 

OpenLDAP schema 非必填 非open access_用户创建


也就是说该进程的组ID和a.out文件的组ID匹配成功,因为a.out文件的所属群组ID未更改,只是更改了文件所有者。

$ chmod u+s a.out 
-rwsrwxr-x 1 root           15945 Nov 30 12:10 a.out
$ ./a.out /etc/shadow
access error for /etc/shadow: Permission denied
access error for /etc/shadow: Permission denied
access error for /etc/shadow: Permission denied
open for reading /etc/shadow OK.

可以看到此时a.out可执行文件的权限位有SUID位,《Unix环境高级编程》中给出的解释是:‘‘When this file is executed, set the effective user ID of the process to be the owner of the file (st_uid).’’
那么当新创建的进程(要执行a.out可执行文件的进程)发现SUID位时,在exec()函数中内核(kernel)就会临时把该进程的有效用户ID更改为a.out的文件拥有者ID。那么此时该进程的有效用户ID为root,调用open函数访问/etc/shadow文件时,用户ID匹配成功,所以能够以只读方式打开shadow文件。
但是此时该进程的实际用户ID仍为普通用户ID,那么当用access函数测试时,就得不到/etc/shadow文件的任何访问权限,得到访问拒绝的结果,从结果中前三行可以看到。

总结:

这篇博客很好的诠释了Linux中有效用户id、实际用户id、设置用户id的区别及进程访问文件机制,对我帮助很大。也欢迎大家评论留言!