linux 可執(zhí)行文件與寫(xiě)操作的同步問(wèn)題(文件讀寫(xiě)操作產(chǎn)生的鎖機(jī)制)
Inode結(jié)點(diǎn)中包含一個(gè)數(shù)據(jù)項(xiàng),叫做i_writecount,很明顯是用于記錄文件被寫(xiě)的個(gè)數(shù)的,用于同步的,其類(lèi)型也是atomic_t. 內(nèi)核中有兩個(gè)我們需要了解的函數(shù),與write操作有關(guān),分別是:
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) {//如果文件被打開(kāi)了,返回失敗
spin_unlock(&inode->i_lock);
return -ETXTBSY;
}
atomic_dec(&inode->i_writecount);
spin_unlock(&inode->i_lock);
}
這兩個(gè)函數(shù)都很簡(jiǎn)單,get_write_acess作用就和名稱(chēng)一致,同樣deny_write_access也是。如果一個(gè)文件被執(zhí)行了,要保證它在執(zhí)行的過(guò)程中不能被寫(xiě),那么在開(kāi)始執(zhí)行前應(yīng)該調(diào)用deny_write_access 來(lái)關(guān)閉寫(xiě)的權(quán)限。那就來(lái)檢查execve系統(tǒng)調(diào)用有沒(méi)有這么做。
Sys_execve中調(diào)用do_execve,然后又調(diào)用函數(shù)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);//調(diào)用
if (err)
goto exit;
out:
return file;
exit:
fput(file);
return ERR_PTR(err);
}
明顯看到了deny_write_access的調(diào)用,和預(yù)想的完全一致。在open的調(diào)用里,應(yīng)該有g(shù)et_write_access的調(diào)用。在open調(diào)用相關(guān)的__dentry_open函數(shù)中就包含了對(duì)該函數(shù)的調(diào)用。
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.
那么內(nèi)核又是如何保證一個(gè)正在被寫(xiě)的文件是不允許被執(zhí)行的呢?這個(gè)同樣也很簡(jiǎn)單,當(dāng)一個(gè)文件已經(jīng)為write而open時(shí),它對(duì)應(yīng)的inode的i_writecount會(huì)變成1,因此在執(zhí)行execve時(shí)同樣會(huì)調(diào)用deny_write_access 中讀取到i_writecount>0之后就會(huì)返回失敗,因此execve也就會(huì)失敗返回。
這里是寫(xiě)文件與i_writecount相關(guān)的場(chǎng)景:
寫(xiě)打開(kāi)一個(gè)文件時(shí),在函數(shù)dentry_open中:
if (f->f_mode & FMODE_WRITE) {
error = get_write_access(inode);
if (error)
goto cleanup_file;
}
當(dāng)然在文件關(guān)閉時(shí),會(huì)將i_writecount--;關(guān)閉時(shí)會(huì)執(zhí)行代碼:
if (file->f_mode & FMODE_WRITE)
put_write_access(inode);
put_write_access 代碼很簡(jiǎn)單:
static inline void put_write_access(struct inode * inode)
{
atomic_dec(&inode->i_writecount);
}
于是乎自己寫(xiě)了個(gè)簡(jiǎn)單的代碼,一個(gè)空循環(huán),文件在執(zhí)行的時(shí)候,在bash中,echo 111 >>可執(zhí)行文件,結(jié)果預(yù)期之中,返回失敗,并提示信息 text file busy.
那么該機(jī)制是否同樣適用于映射機(jī)制呢,在執(zhí)行可執(zhí)行文件時(shí),會(huì)mmap一些關(guān)聯(lián)的動(dòng)態(tài)鏈接庫(kù),這些動(dòng)態(tài)鏈接庫(kù)是否被mmap之后就不允許被寫(xiě)以及正在寫(xiě)時(shí)不允許mmap呢?這個(gè)是需要考慮的,因?yàn)樗P(guān)系到安全的問(wèn)題。因?yàn)閹?kù)文件也是可執(zhí)行的代碼,被篡改同樣會(huì)引起安全問(wèn)題。
Mmap在調(diào)用mmap_region的函數(shù)里,有一個(gè)相關(guān)的檢查:
if (vm_flags & VM_DENYWRITE) {
error = deny_write_access(file);
if (error)
goto free_vma;
correct_wcount = 1;
}
其中,mmap調(diào)用中的flags參數(shù)會(huì)被正確的賦值給vm_flags,對(duì)應(yīng)關(guān)系是MAP_DENYWRIRE被設(shè)置了,那么VM_DENYWRITE就對(duì)應(yīng)的也被設(shè)置。下面寫(xiě)了個(gè)簡(jiǎn)單的代碼,做一下測(cè)試:
#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被寫(xiě)成了”0000”,很奇怪,貌似MAP_DENTWRITE不起作用了。于是man mmap查看,發(fā)現(xiàn):
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.)
原來(lái)這個(gè)標(biāo)識(shí)在用戶層已經(jīng)不起作用了啊,而且還說(shuō)明了原因,容易引起拒絕式服務(wù)攻擊。攻擊者惡意的將某些系統(tǒng)程序要寫(xiě)的文件以MAP_DENYWRITE模式映射,會(huì)導(dǎo)致正常程序?qū)懳募?。不過(guò)VM_DENYWRITE在內(nèi)核里還是有使用的,在mmap中還是有對(duì)deny_write_access的調(diào)用, 但是對(duì)它的調(diào)用已經(jīng)不是由mmap中的flag參數(shù)的MAP_DENYWRITE驅(qū)動(dòng)的了。
那與可執(zhí)行文件相關(guān)的動(dòng)態(tài)鏈接庫(kù)文件就悲劇了,大家都知道動(dòng)態(tài)鏈接庫(kù)使用的也是mmap,這也導(dǎo)致動(dòng)態(tài)鏈接庫(kù)在運(yùn)行時(shí)可以被更改。其實(shí)我這就是為了確認(rèn)這點(diǎn)。這也導(dǎo)致我需要自己寫(xiě)同步控制代碼了。我們可以使用inode中的i_security以及file結(jié)構(gòu)的f_secutiry變量來(lái)寫(xiě)自己的同步邏輯,就是麻煩了不少,還要寫(xiě)內(nèi)核模塊,哎,工作量又增加了啊。安全問(wèn)題是個(gè)麻煩的問(wèn)題...
相關(guān)文章
詳解Linux系統(tǒng)中如何修改時(shí)間和時(shí)區(qū)
當(dāng)我們?cè)谑褂肔inux系統(tǒng)時(shí),有時(shí)會(huì)遇到系統(tǒng)時(shí)間與當(dāng)?shù)貢r(shí)間存在差異的情況,所以這篇文章就給大家詳細(xì)介紹如何解決這個(gè)問(wèn)題,感興趣的小伙伴跟著小編一起來(lái)學(xué)習(xí)吧2023-07-07快速搭建簡(jiǎn)易、高效、多線程http服務(wù)器
通過(guò)Node.js來(lái)快速高效的搭建一個(gè)高性能http服務(wù)器,github上Charlie Robbins分享了一個(gè)開(kāi)源項(xiàng)目,讓大家都可以輕松的搭建臨時(shí)高性能http服務(wù)器2018-02-02Linux如何設(shè)置java.library.path
這篇文章主要介紹了Linux如何設(shè)置java.library.path問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Ubuntu Server 10.04修改Apache的默認(rèn)目錄的方法
這篇文章主要為大家分享下Ubuntu Server 10.04修改Apache的默認(rèn)目錄的方法,需要的朋友可以參考下2013-12-12Linux實(shí)現(xiàn)自動(dòng)掛載autofs的方法詳解
這篇文章主要介紹了Linux實(shí)現(xiàn)自動(dòng)掛載autofs的相關(guān)資料。autofs 服務(wù)將實(shí)現(xiàn)自動(dòng)掛載外圍設(shè)備,NFS共享目錄等,并在空閑5分鐘后后自動(dòng)卸載,需要的可以參考一下2022-10-10