linux 可执行文件与写操作的同步问题
当一个可执行文件已经为write而open时,此时的可执行文件是不允许被执行的。反过来,一个文件正在执行时,它也是不允许同时被write模式而open的。这个约束很好理解,因为文件执行和文件被写应该需要同步保护,因此内核会保证这种同步。
那么内核是如何实现该机制的呢?
Inode结点中包含一个数据项,叫做i_writecount,很明显是用于记录文件被写的个数的,用于同步的,其类型也是atomic_t. 内核中有两个我们需要了解的函数,与write操作有关,分别是:
int get_write_access(struct inode * inode)
{
spin_lock(&inode->i_lock);
if (atomic_read(&inode->i_writecount) < 0) {
spin_unlock(&inode->i_lock);
return -ETXTBSY;
}
atomic_inc(&inode->i_writecount);
spin_unlock(&inode->i_lock);
return 0;
} int deny_write_access(struct file * file)
{
struct inode *inode = file->f_path.dentry->d_inode;
spin_lock(&inode->i_lock);
if (atomic_read(&inode->i_writecount) > 0) {//如果文件被打开了,返回失败
spin_unlock(&inode->i_lock);
return -ETXTBSY;
}
atomic_dec(&inode->i_writecount);
spin_unlock(&inode->i_lock);
}
这两个函数都很简单,get_write_acess作用就和名称一致,同样deny_write_access也是。如果一个文件被执行了,要保证它在执行的过程中不能被写,那么在开始执行前应该调用deny_write_access 来关闭写的权限。那就来检查execve系统调用有没有这么做。
Sys_execve中调用do_execve,然后又调用函数open_exec,看一下open_exec的代码:
struct file *open_exec(const char *name)
{
struct file *file;
int err;
file = do_filp_open(AT_FDCWD, name,
O_LARGEFILE | O_RDONLY | FMODE_EXEC, 0,
MAY_EXEC | MAY_OPEN); if (IS_ERR(file))
goto out;
err = -EACCES; if (!S_ISREG(file->f_path.dentry->d_inode->i_mode))
goto exit; if (file->f_path.mnt->mnt_flags & MNT_NOEXEC)
goto exit; fsnotify_open(file->f_path.dentry);
err = deny_write_access(file);//调用
if (err)
goto exit; out:
return file; exit:
fput(file);
return ERR_PTR(err);
}
明显看到了deny_write_access的调用,和预想的完全一致。在open的调用里,应该有get_write_access的调用。在open调用相关的__dentry_open函数中就包含了对该函数的调用,
if (f->f_mode & FMODE_WRITE) {
error = __get_file_write_access(inode, mnt);
if (error)
goto cleanup_file;
if (!special_file(inode->i_mode))
file_take_write(f);
}
其中__get_file_write_access(inode, mnt)封装了get_write_access.
那么内核又是如何保证一个正在被写的文件是不允许被执行的呢?这个同样也很简单,当一个文件已经为write而open时,它对应的inode的i_writecount会变成1,因此在执行execve时同样会调用deny_write_access 中读取到i_writecount>0之后就会返回失败,因此execve也就会失败返回。
这里是写文件与i_writecount相关的场景:
写打开一个文件时,在函数dentry_open中:
if (f->f_mode & FMODE_WRITE) {
error = get_write_access(inode);
if (error)
goto cleanup_file;
}
当然在文件关闭时,会将i_writecount--;关闭时会执行代码:
if (file->f_mode & FMODE_WRITE)
put_write_access(inode);
put_write_access 代码很简单:
static inline void put_write_access(struct inode * inode)
{
atomic_dec(&inode->i_writecount);
}
于是乎自己写了个简单的代码,一个空循环,文件在执行的时候,在bash中,echo 111 >>可执行文件,结果预期之中,返回失败,并提示信息 text file busy.
那么该机制是否同样适用于映射机制呢,在执行可执行文件时,会mmap一些关联的动态链接库,这些动态链接库是否被mmap之后就不允许被写以及正在写时不允许mmap呢?这个是需要考虑的,因为它关系到安全的问题。因为库文件也是可执行的代码,被篡改同样会引起安全问题。
Mmap在调用mmap_region的函数里,有一个相关的检查:
if (vm_flags & VM_DENYWRITE) {
error = deny_write_access(file);
if (error)
goto free_vma;
correct_wcount = 1;
}
其中,mmap调用中的flags参数会被正确的赋值给vm_flags,对应关系是MAP_DENYWRIRE被设置了,那么VM_DENYWRITE就对应的也被设置。下面写了个简单的代码,做一下测试:
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd;
void *src = NULL;
fd = open("test.txt",O_RDONLY);
if (fd != 0)
{
if ((src = mmap(0,5,PROT_READ|PROT_EXEC ,MAP_PRIVATE| MAP_DENYWRITE,fd,0))== MAP_FAILED)
{
printf("MMAP error\n");
printf("%s\n",strerror(errno));
}else{
printf("%x\n",src);
}
} FILE * fd_t = fopen("test.txt","w");
if( !fd_t)
{
printf("open for write error\n");
printf("%s\n",strerror(errno));
return 0;
} if (fwrite("0000",sizeof(char),4,fd_t) != 4)
{
printf("fwrite error \n");
} fclose(fd_t);
close(fd);
return 1;
}
最后的test.txt被写成了”0000”,很奇怪,貌似MAP_DENTWRITE不起作用了。于是man mmap查看,发现:
MAP_DENYWRITE
This flag is ignored. (Long ago, it signaled that attempts to write to the underlying file should fail with ETXTBUSY. But this was a source of denial-of-service attacks.)
原来这个标识在用户层已经不起作用了啊,而且还说明了原因,容易引起拒绝式服务攻击。攻击者恶意的将某些系统程序要写的文件以MAP_DENYWRITE模式映射,会导致正常程序写文件失败。不过VM_DENYWRITE在内核里还是有使用的,在mmap中还是有对deny_write_access的调用, 但是对它的调用已经不是由mmap中的flag参数的MAP_DENYWRITE驱动的了。
那与可执行文件相关的动态链接库文件就悲剧了,大家都知道动态链接库使用的也是mmap,这也导致动态链接库在运行时可以被更改。其实我这就是为了确认这点。这也导致我需要自己写同步控制代码了。我们可以使用inode中的i_security以及file结构的f_secutiry变量来写自己的同步逻辑,就是麻烦了不少,还要写内核模块,哎,工作量又增加了啊。安全问题是个麻烦的问题...
linux 可执行文件与写操作的同步问题的更多相关文章
- 关于异步IO与同步IO的写操作区别
最近这两天都在看IO相关的知识点.一开始太凌乱,太杂,不过终于整理清楚了.觉得杂乱是因为一开始以为异步IO等于非阻塞IO,这完全是两个概念, LINUX下的异步IO有两类,一类为glibc AIO,这 ...
- Linux Kernel文件系统写I/O流程代码分析(一)
Linux Kernel文件系统写I/O流程代码分析(一) 在Linux VFS机制简析(二)这篇博客上介绍了struct address_space_operations里底层文件系统需要实现的操作 ...
- NoSQL生态系统——事务机制,行锁,LSM,缓存多次写操作,RWN
13.2.4 事务机制 NoSQL系统通常注重性能和扩展性,而非事务机制. 传统的SQL数据库的事务通常都是支持ACID的强事务机制.要保证数据的一致性,通常多个事务是不可能交叉执行的,这样就导致了可 ...
- Linux数据写操作改进
Linux的IO操作中数据的写函数int nwrite = write(int fd,void* buf ,int len)表示向fd文件描述符写入len个字节长度的数据报文,但是这并不能保证真正向内 ...
- 管道的原子性 linux写操作原子性
从本质上说,管道也是一种文件,但他又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题 限制管道的大小.实际上,管道是一个固定大小的缓冲区.在Linux中该换冲区的大小为一页,4k 使得他的 ...
- Linux下多任务间通信和同步-信号
Linux下多任务间通信和同步-信号 嵌入式开发交流群280352802,欢迎加入! 1.概述 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式.信号可以直接进行用户空间进程和内核进程之间的 ...
- 24小时学通Linux内核之调度和内核同步
心情大好,昨晚我们实验室老大和我们聊了好久,作为已经在实验室待了快两年的大三工科男来说,老师让我们不要成为那种技术狗,代码工,说多了都是泪啊,,不过我们的激情依旧不变,老师帮我们组好了队伍,着手参加明 ...
- Linux下多任务间通信和同步-mmap共享内存
Linux下多任务间通信和同步-mmap共享内存 嵌入式开发交流群280352802,欢迎加入! 1.简介 共享内存可以说是最有用的进程间通信方式.两个不用的进程共享内存的意思是:同一块物理内存被映射 ...
- 8)Linux程序设计入门--线程操作
)Linux程序设计入门--线程操作 前言:Linux下线程的创建 介绍在Linux下线程的创建和基本的使用. Linux下的线程是一个非常复杂的问题,由 于我对线程的学习不时很好,我在这里只是简单的 ...
随机推荐
- 在终端上创建Java项目及编译和运行
一:实践一次这样的操作有助于理解Tomcat/Eclipse的启动原理,包括classpath的设置,option的配置等等: 二:通过Bash终端创建一个简单的Java项目(单项目单Module,如 ...
- movielens 时间戳是秒级别的
sigmoid(inX)函数 def sigmoid(inX): return 1.0/(1+exp(-inX)) Timestamps represent seconds since midnigh ...
- python调用dll方法
在python中调用dll文件中的接口比较简单,实例代码如下: 如我们有一个test.dll文件,内部定义如下: extern "C"{ int __stdcall test( v ...
- MarkdownPad 2.x破解下载
Markdown是一种轻量级的标记语言,目前有不少Markdown编辑器,其他的编辑器,诸如:Notepad++.Sublime Text 2也通过插件添加了支持.Markdown的特点就是易读易写, ...
- VS2010程序打包操作--超详细
1. 在vs2010 选择“新建项目”----“其他项目类型”----“Visual Studio Installerà“安装项目”: 命名为:Setup1 . 这是在VS2010中将有三个文件夹, ...
- SSH整合 第三篇 Spring的加入
1.思路和想法. 目前理解到的,觉得是的,可能的,应该这样的……………… Spring的两大核心是IoC和AOP Ioc:帮助实例化对象,加载到容器中,在注入到需要用到的地方.这样就可以减少在不同的方 ...
- hdu1158 Employment Planning 2016-09-11 15:14 33人阅读 评论(0) 收藏
Employment Planning Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Othe ...
- APUE(5)---标准I/O库 (3)
十.定位流 #include <stdio.h> long ftell(FILE *fp); //若成功,返回当前文件位置指示:若出错,返回-1L int fseek(FILE *fp, ...
- Oracle EBS标准错误信息如何追踪 (Debug)
http://www.cnblogs.com/songdavid/articles/2067534.html 调用EBS标准API的时候,可能会返回一些让人看不懂的错误,比如最近我在开发rcv_tra ...
- Java面向接口编程【精品博客】
我们从生活中去理解面向接口编程,以下举例四个案例来理解: 案例一(汽车案例): /** * 汽车标准接口 * @author Liudeli */ public interface ICar { /* ...