當一個可執行文件已經爲write而open時,此時的可執行文件是不容許被執行的。反過來,一個文件正在執行時,它也是不容許同時被write模式而open的。這個約束很好理解,由於文件執行和文件被寫應該須要同步保護,所以內核會保證這種同步。node
那麼內核是如何實現該機制的呢?安全
Inode結點中包含一個數據項,叫作i_writecount,很明顯是用於記錄文件被寫的個數的,用於同步的,其類型也是atomic_t. 內核中有兩個咱們須要瞭解的函數,與write操做有關,分別是:bash
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函數中就包含了對該函數的調用,this
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.atom
那麼內核又是如何保證一個正在被寫的文件是不容許被執行的呢?這個一樣也很簡單,當一個文件已經爲write而open時,它對應的inode的i_writecount會變成1,所以在執行execve時一樣會調用deny_write_access 中讀取到i_writecount>0以後就會返回失敗,所以execve也就會失敗返回。blog
這裏是寫文件與i_writecount相關的場景:
寫打開一個文件時,在函數dentry_open中:ci
if (f->f_mode & FMODE_WRITE) { error = get_write_access(inode); if (error) goto cleanup_file; }
固然在文件關閉時,會將i_writecount--;關閉時會執行代碼:get
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變量來寫本身的同步邏輯,就是麻煩了很多,還要寫內核模塊,哎,工做量又增長了啊。安全問題是個麻煩的問題...