当一个可执行文件已经为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 可执行文件与写操作的同步问题的更多相关文章

  1. 关于异步IO与同步IO的写操作区别

    最近这两天都在看IO相关的知识点.一开始太凌乱,太杂,不过终于整理清楚了.觉得杂乱是因为一开始以为异步IO等于非阻塞IO,这完全是两个概念, LINUX下的异步IO有两类,一类为glibc AIO,这 ...

  2. Linux Kernel文件系统写I/O流程代码分析(一)

    Linux Kernel文件系统写I/O流程代码分析(一) 在Linux VFS机制简析(二)这篇博客上介绍了struct address_space_operations里底层文件系统需要实现的操作 ...

  3. NoSQL生态系统——事务机制,行锁,LSM,缓存多次写操作,RWN

    13.2.4 事务机制 NoSQL系统通常注重性能和扩展性,而非事务机制. 传统的SQL数据库的事务通常都是支持ACID的强事务机制.要保证数据的一致性,通常多个事务是不可能交叉执行的,这样就导致了可 ...

  4. Linux数据写操作改进

    Linux的IO操作中数据的写函数int nwrite = write(int fd,void* buf ,int len)表示向fd文件描述符写入len个字节长度的数据报文,但是这并不能保证真正向内 ...

  5. 管道的原子性 linux写操作原子性

    从本质上说,管道也是一种文件,但他又和一般的文件有所不同,管道可以克服使用文件进行通信的两个问题 限制管道的大小.实际上,管道是一个固定大小的缓冲区.在Linux中该换冲区的大小为一页,4k 使得他的 ...

  6. Linux下多任务间通信和同步-信号

    Linux下多任务间通信和同步-信号 嵌入式开发交流群280352802,欢迎加入! 1.概述 信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式.信号可以直接进行用户空间进程和内核进程之间的 ...

  7. 24小时学通Linux内核之调度和内核同步

    心情大好,昨晚我们实验室老大和我们聊了好久,作为已经在实验室待了快两年的大三工科男来说,老师让我们不要成为那种技术狗,代码工,说多了都是泪啊,,不过我们的激情依旧不变,老师帮我们组好了队伍,着手参加明 ...

  8. Linux下多任务间通信和同步-mmap共享内存

    Linux下多任务间通信和同步-mmap共享内存 嵌入式开发交流群280352802,欢迎加入! 1.简介 共享内存可以说是最有用的进程间通信方式.两个不用的进程共享内存的意思是:同一块物理内存被映射 ...

  9. 8)Linux程序设计入门--线程操作

    )Linux程序设计入门--线程操作 前言:Linux下线程的创建 介绍在Linux下线程的创建和基本的使用. Linux下的线程是一个非常复杂的问题,由 于我对线程的学习不时很好,我在这里只是简单的 ...

随机推荐

  1. hadoop学习笔记(四):hdfs常用命令

    一.hadoop fs 1.创建目录 [root@master hadoop-]# hadoop fs -mkdir /testdir1 [root@master hadoop-]# hadoop f ...

  2. Spinner功能和用法

    书中只是简单写了选择的界面,没有写出选择之后的结果显示,我做了进一步功能. MainActivity.java public class MainActivity extends Activity { ...

  3. GitBash入门

    转载自:http://www.cnblogs.com/randomsteps/p/5415116.html 作为一个初学者,我是跟着廖学峰老师的官方博客学习,这里只是做个笔记,哈哈,关于git的历史. ...

  4. Vivado 常见报错

    1.[Synth 8-2543] port connections cannot be mixed ordered and named 说明例化时最后一个信号添加了一个逗号. 2. 原因:报告说明有一 ...

  5. hdu 1505,1506

    1506题目 1505题目 1506: #include<stdio.h> #include<string.h> #include<iostream> using ...

  6. java Map集合学习

    学习语法还是从例子着手: FileDao fileDao=new FileBeanDaoImpl(); FileBean fileBean=new FileBean(); listBean=fileD ...

  7. Mac突然没有声音但是重启后可以恢复

    命令行操作方式   今天又发现了Mac上的一个BUG,有时候在工作之余我们去吃饭的时候Mac经常会进入睡眠状态,但是有的时候从睡眠状态激活后,本来想听个音乐,但是突然发现音乐不可以用了,以前每次都是重 ...

  8. MySQL Route负载均衡与读写分离Docker环境使用

    Docker环境描述 主机名 部署服务 备注 MySQL Route MySQL Route 部署在宿主机上的MySQL Route服务 MySQL Master1 MySQL 5.7.16 Dock ...

  9. 纸壳CMS可视化建站系统搭建多语言网站

    纸壳CMS是可视化建站系统,现已经从架构上支持多语言.但是多语言功能默认是没有开启的.您可以从设置中开启多语言,或者随时关闭它,您可以随时进行切换. 开启多语言 如果您没有在系统设置中看到多语言设置菜 ...

  10. BitAdminCore框架应用篇:(三)核心套件querySuite入门介绍

    索引 NET Core应用框架之BitAdminCore框架应用篇系列 框架演示:http://bit.bitdao.cn 框架源码:https://github.com/chenyinxin/coo ...