linux 可執行文件與寫操做的同步問題

  當一個可執行文件已經爲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變量來寫本身的同步邏輯,就是麻煩了很多,還要寫內核模塊,哎,工做量又增長了啊。安全問題是個麻煩的問題...

相關文章
相關標籤/搜索