测试环境是3.10.0 内核。

有一次操作中,发现cp -f A B执行的时候,行为不一样:

当B没被打开,则正常覆盖B。

当B是被打开,但没有被执行,则能覆盖,

当B被打开,且被执行,则不能直接覆盖,而是创建一个同名文件,然后写这个文件,同时B的inode在os中用lsof看的话,是delete。

问题是:为什么被执行的文件不能覆盖,它通过什么机制保护的?

通过strace,发现cp -f的时候,如果目标文件正在被执行,那么返回的是 ETXTBSY,为什么会返回busy,以及这个busy是怎么设置的呢?

stap 一下:

  1. probe kernel.function("do_dentry_open").return
  2. {
  3. if($return == -)---------这个就是busy的错误码
  4. {
  5. print_backtrace();
  6. exit();
  7. }
  8. }

通过搜索并stap内核代码,发现流程如下:

  1. Returning from: 0xffffffff812062d0 : do_dentry_open+0x0/0x2e0 [kernel]
  2. Returning to : 0xffffffff8120664a : vfs_open+0x5a/0xb0 [kernel]
  3. 0xffffffff812179ad : do_last+0x1ed/0x12c0 [kernel]
  4. 0xffffffff816be459 : kretprobe_trampoline+0x0/0x57 [kernel]
  5. 0xffff88557427ffd8
  6. 0xffffffff8121b0eb : do_filp_open+0x4b/0xb0 [kernel] (inexact)
  7. 0xffffffff81125a87 : __audit_getname+0x97/0xb0 [kernel] (inexact)
  8. 0xffffffff8122840a : __alloc_fd+0x8a/0x130 [kernel] (inexact)
  9. 0xffffffff81207a13 : do_sys_open+0xf3/0x1f0 [kernel] (inexact)
  10. 0xffffffff81207b2e : sys_open+0x1e/0x20 [kernel] (inexact)
  11. 0xffffffff816c5991 : tracesys+0x9d/0xc3 [kernel] (inexact)
  1. static int do_dentry_open(struct file *f,
  2. struct inode *inode,
  3. int (*open)(struct inode *, struct file *),
  4. const struct cred *cred)
  5. {
  6. static const struct file_operations empty_fops = {};
  7. int error;
  8.  
  9. f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK |
  10. FMODE_PREAD | FMODE_PWRITE;
  11.  
  12. if (unlikely(f->f_flags & O_PATH))
  13. f->f_mode = FMODE_PATH;
  14.  
  15. path_get(&f->f_path);
  16. f->f_inode = inode;
  17. if (f->f_mode & FMODE_WRITE) {
  18. error = __get_file_write_access(inode, f->f_path.mnt);
  1. static inline int __get_file_write_access(struct inode *inode,
  2. struct vfsmount *mnt)
  3. {
  4. int error;
  5. error = get_write_access(inode);

报错的最终函数就是get_write_access:

  1. static inline int get_write_access(struct inode *inode)
  2. {
  3. return atomic_inc_unless_negative(&inode->i_writecount) ? : -ETXTBSY;
  4. }

看来,文件在被执行的时候,会将 inode->i_writecount 值会被设置为负值?查看代码,调用链是:

  1. stub_execve -->sys_execve-->do_execve_common-->do_open_exec-->deny_write_access

我照样stap一下:

一个gdb程序用来打开一个文件,然后gdb断住:

  1. (gdb) n
  2. FILE *pFile=NULL;
  3. (gdb)
  4. char * Mode="a+";
  5. (gdb)
  6. int ret=;
  7. (gdb)
  8. pFile = fopen("main.o", Mode);
  9. (gdb)
  10. if(!pFile)
  11. (gdb)
  12. ret=fputs("abcd", pFile);

另外一边,使用stap进行跟踪:

  1. probe kernel.function("do_open_exec").return
  2. {
  3. if($return == -)
  4. {
  5. print_backtrace();
  6. exit();
  7. }
  8.  
  9. }

然后第三个窗口执行./main.o:

  1. strace ./main.o
  2. execve("./main.o", ["./main.o"], [/* 38 vars */]) = - ETXTBSY (Text file busy)
  3. write(, "strace: exec: Text file busy\n", 29strace: exec: Text file busy
  4. ) =
  5. exit_group() = ?
  6. +++ exited with +++

发现确实报错的是busy,也就是 deny_write_access 返回busy。

stap的结果是:

  1. [root@localhost code]# stap cp_fail.stp
  2. System Call Monitoring Started ( seconds)...
  3. Returning from: 0xffffffff8120ffe0 : do_open_exec+0x0/0x100 [kernel]
  4. Returning to : 0xffffffff81210f73 : do_execve_common.isra.+0x203/0x6c0 [kernel]
  5. 0xffffffff812116c9 : sys_execve+0x29/0x30 [kernel]
  6. 0xffffffff816c5c98 : stub_execve+0x48/0x80 [kernel]

当然,如果执行在前,而open再写入在后,则报busy的是后面的open。

阶段性总结一下:

对于文件的写入,内核需要判断当前文件是否正在被执行,如果在的话,则拒绝写入,当一个可执行文件已经为write而open时,此时的可执行文件是不允许被执行的。反过来,一个文件正在执行时,它也是不允许同时被write模式而open的。相关函数如下。

  1. /*
  2. * get_write_access() gets write permission for a file.
  3. * put_write_access() releases this write permission.
  4. * This is used for regular files.
  5. * We cannot support write (and maybe mmap read-write shared) accesses and
  6. * MAP_DENYWRITE mmappings simultaneously. The i_writecount field of an inode
  7. * can have the following values:
  8. * 0: no writers, no VM_DENYWRITE mappings
  9. * < 0: (-i_writecount) vm_area_structs with VM_DENYWRITE set exist
  10. * > 0: (i_writecount) users are writing to the file.--------------------------可以有多个write
  11. *
  12. * Normally we operate on that counter with atomic_{inc,dec} and it's safe
  13. * except for the cases where we don't hold i_writecount yet. Then we need to
  14. * use {get,deny}_write_access() - these functions check the sign and refuse
  15. * to do the change if sign is wrong.
  16. */
  1. static inline int get_write_access(struct inode *inode)
  2. {
  3. return atomic_inc_unless_negative(&inode->i_writecount) ? : -ETXTBSY;
  4. }
  5. static inline int deny_write_access(struct file *file)
  6. {
  7. struct inode *inode = file_inode(file);
  8. return atomic_dec_unless_positive(&inode->i_writecount) ? : -ETXTBSY;
  9. }
  10. static inline void put_write_access(struct inode * inode)
  11. {
  12. atomic_dec(&inode->i_writecount);
  13. }
  14. static inline void allow_write_access(struct file *file)
  15. {
  16. if (file)
  17. atomic_inc(&file_inode(file)->i_writecount);
  18. }
  19. static inline bool inode_is_open_for_write(const struct inode *inode)
  20. {
  21. return atomic_read(&inode->i_writecount) > ;
  22. }

接下来,描述一下故障现场,当时的情况是,nginx程序正在运行,系统升级,有测试人员将so库使用cp -f 覆盖,由于此时某些so已经被该程序所使用,现在把这些so文件覆盖了,导致了该程序崩溃。结果正在运行的nginx出现了段错误,

  1. [6908922.939768] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  2. [6908922.947471] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  3. [6908922.947851] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  4. [6908922.949222] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fff70ef6508 error in nginx (deleted)[+46c000]
  5. [6908922.960546] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  6. [6908922.962161] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fff70ef6508 error in nginx (deleted)[+46c000]
  7. [6908927.932537] show_signal_msg: callbacks suppressed
  8. [6908927.932543] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  9. [6908927.934137] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  10. [6908927.936796] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  11. [6908927.938551] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  12. [6908927.941886] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  13. [6908927.943306] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  14. [6908927.945299] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  15. [6908927.949962] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  16. [6908927.950502] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]
  17. [6908927.953350] nginx[]: segfault at 73c6 ip 00000000000073c6 sp 00007fffeb5e7868 error in nginx[+]

按照之前的分析,正在运行的代码,是不能覆盖的,那为什么动态库被覆盖了呢?下面来分析一下动态链接库的调用。

  1. [root@localhost code]# cat dll.c
  2. #include <stdio.h>
  3. void dll_function(const char* szString)
  4. {
  5. printf("%s\n", szString);
  6. }
  1. cat dll_main.c
  2. void dll_function(const char* szString);
  3. int main()
  4. {
  5. dll_function("call dll now!!!!!!");
  6. return ;
  7. }
  1. [root@localhost code]# gcc -c -fPIC dll.c
  2. [root@localhost code]# gcc -shared -fPIC -o libdllfun.so dll.o
  3. [root@localhost code]# cp libdllfun.so /usr/lib
  4. [root@localhost code]# gcc -o dll_test dll_main.c -L. -ldllfun
  1. strace ./dll_test
  2. execve("./dll_test", ["./dll_test"], [/* 38 vars */]) =
  3. brk(NULL) = 0x2254000
  4. mmap(NULL, , PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -, ) = 0x7f024eb8f000
  5. access("/etc/ld.so.preload", R_OK) = - ENOENT (No such file or directory)
  6. open("/usr/lib/tls/x86_64/libdllfun.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
  7. stat("/usr/lib/tls/x86_64", 0x7ffe6791bd40) = - ENOENT (No such file or directory)
  8. open("/usr/lib/tls/libdllfun.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
  9. stat("/usr/lib/tls", 0x7ffe6791bd40) = - ENOENT (No such file or directory)
  10. open("/usr/lib/x86_64/libdllfun.so", O_RDONLY|O_CLOEXEC) = - ENOENT (No such file or directory)
  11. stat("/usr/lib/x86_64", 0x7ffe6791bd40) = - ENOENT (No such file or directory)
  12. open("/usr/lib/libdllfun.so", O_RDONLY|O_CLOEXEC) = 3-----------------------找到并打开对应的dll文件
  13. read(, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340\5\0\0\0\0\0\0"..., ) =
  14. fstat(, {st_mode=S_IFREG|, st_size=, ...}) =
  15. mmap(NULL, , PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, , ) = 0x7f024e76d000-----------map对应的代码

mprotect(0x7f024e76e000, 2093056, PROT_NONE) = 0
  mmap(0x7f024e96d000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0x7f024e96d000
  close(3)-------------------文件关闭

  1.  

由测试代码可知,linux加载动态库是先读取elf头,然后使用的mmap方式加载代码,虽然也有open,但open的时候是O_RDONLY|O_CLOEXEC

而不是我们前面例子里面的wirte方式打开,既然如此,那么文件中的内容在被异常更改之后,就可能执行代码出错。为此,我也测试了一下,确实如此,有时候出现段错误,有时候

出现sigbus,有时候一点问题没有,完全取决于你修改的文件的位置。

  1. dll_function32("call 32!!!!!!");
  2. (gdb)
  3.  
  4. Program received signal SIGSEGV, Segmentation fault.
  5.  
  6. 或者:
  7. dll_function32("call 32!!!!!!");
  8. (gdb)
  9.  
  10. Program received signal SIGBUS, Bus error.

然而-----------------------------------------

测试人员同意了我的判断,然后他做了一个变态的事情,就是把所有的lib库备份到一个路径,然后等nginx运行之后,将原路径的lib删除,再这个lib文件拷贝回去,结果发现,还是段错误了。

我看了一下前后的i节点,都不一样了,不段错误才怪。

然后测试人员还是不死心,使用mv的方式来修改,结果发现一切正常,因为inode没变,文件的内容页没变,所以没有问题。

另外,有必要提一下,如果使用cp -rf来拷贝,当源文件是一个实体文件,而目的文件是一个软连接的时候,其实最终会将源实体文件拷贝到目的文件所指向的实体文件。

还有个细节,cp -rf 的时候,cp的顺序,取决于ls -u看到的顺序,而ls -u看到的顺序,取决于文件系统实现。

 

linux 覆盖可执行文件的问题的更多相关文章

  1. Linux C++ 调试神技--如何将Linux C++ 可执行文件逆向工程到Intel格式汇编

    Linux C++ 调试神技--如何将Linux C++ 可执行文件逆向工程到Intel格式汇编 对于许多在windows 上调试代码的人而言, Intel IA32格式的汇编代码可能并不陌生,因为种 ...

  2. [转]linux,windows 可执行文件(ELF、PE)

    ELF (Executable Linkable Format)UNIX类操作系统中普遍采用的目标文件格式 . 首先要知道它有什么作用:工具接口标准委员会TIS已经将ELF作为运行在Intel32位架 ...

  3. linux下可执行文件的库们

    在Linux下有一些命令可以让我们知道可执行文件的很多信息. 记录如下: ldd : print shared library dependencies nm: list symbols from o ...

  4. linux,windows 可执行文件(ELF、PE)

    现在PC平台流行的可执行文件格式(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format ...

  5. linux 查看可执行文件动态链接库相关信息(转)

    转自 http://blog.sina.com.cn/s/blog_67eb1f2f0100mgd8.html ldd <可执行文件名>       查看可执行文件链接了哪些  系统动态链 ...

  6. golang 在 windows 下编译出 linux 二进制可执行文件的软件套装合集 [go 1.7.3环境]

    golang 很好用,不过要把工具链弄完整. 要不你会发现怎么不能编译跨平台的呀? 怎么写代码没提示啊? ... 这一整套弄下来并不容易. 所以精心准备了一套工具方便大家使用. 软件列表如图. 安装顺 ...

  7. linux执行可执行文件时报xxx:not found

    实际上是因为可执行文件执行时所依赖的动态链接库找不到,解决方法为在编译时加-static表示使用静态链接. 或者使用arm-linux-readelf -d +可执行文件,查看该可执行文件依赖的动态链 ...

  8. linux系统可执行文件添加环境变量使其跨终端和目录执行

    在命令行终端输入:echo $PATH 回车可打印出PATH变量对应的路径 现有一可执行文件qtFirstC,文件所在目录为:/home/lolors/qtFirstC 此时test只能在此目录下运行 ...

  9. go语言在Windows系统下编译成linux系统可执行文件

    package main import ( "fmt" "os" "os/exec" ) //filepath: 要编译的文件的路径 fun ...

随机推荐

  1. TextView-- 测量文字宽度

    https://my.oschina.net/lengwei/blog/637380; http://blog.csdn.net/mare_blue/article/details/51388403; ...

  2. vue 双向数据绑定 Vue事件介绍 以及Vue中的ref获取dom节点

    <template> <div id="app"> <h2>{{msg}}</h2> <input type="te ...

  3. sas transpose 代码备忘

    OPTIONS NOCENTER LS=MAX PS=MAX; LIBNAME S '.\report';/*PROC PRINT DATA=S.doquestionr(WHERE=(sid=1972 ...

  4. a++ 与 ++a 的运算

    var a=5: b=a++和b=++a的区别: 前者是先赋值,再自加,即b=a:a=a+1: //结果b=5,a=6 后者是先自加,再赋值,即a=a+1;b=a;  //结果a=6,b=6

  5. delphi正则表达式学习笔记(三)

    Delphi 中经常使用的正则表达式 在 Delphi 中使用正则表达式, 目前 PerlRegEx 应该是首选, 准备彻底而细致地研究它.  官方网站: http://www.regular-e x ...

  6. android 开发 实现一个带图片Image的ListView

    注意:这种实现方法不是实现ListView的最优方法,只是希望通过练习了解ListView的实现原理 思维路线: 1.创建drawable文件夹将要使用的图片导入进去 2.写一个类,用于存放图片ID数 ...

  7. gentoo intel 安装桌面

    首先增加 vim ~/.xinitrc [[ -f ~/.Xresources ]] && xrdb -merge ~/.Xresources # dbus before fcitx ...

  8. php学习笔记1——使用phpStudy进行php运行环境搭建与测试。

    1. 新手第一步还是使用phpStudy搭建一下windows下的php环境,并测试.如下: http://jingyan.baidu.com/article/3c343ff7067eff0d3679 ...

  9. nohup top & 问题: top: failed tty get

    执行 nohup top & nohup.out 显示 top: failed tty get +++++++++++++++++ top后台执行显示:top: failed tty get ...

  10. NAS 百科 —— http://baike.baidu.com/item/NAS%E7%BD%91%E7%BB%9C%E5%AD%98%E5%82%A8

    NAS(Network Attached Storage)网络存储基于标准网络协议实现数据传输,为网络中的Windows / Linux / Mac OS 等各种不同操作系统的计算机提供文件共享和数据 ...