中fuse_一个Fanotify和FUSE配合使用导致的问题
說明:本文所使用的內核版本為3.10.0-1062.el7.x86_64。
前面文章介紹的fanotify機制可用于監控文件操作產生的事件(比如open, read等),而這些文件會來自不同類型的文件系統,其中比較特殊的一種是FUSE。那fanotify能不能支持對FUSE類型文件系統的監控呢?
【現象】
還是拿fuse.sshfs來進行實驗,這里使用的"sshfs"是基于SFTP文件傳輸協議來掛載遠端目錄的(遠端為sftp server)。不過為了測試的方便(不用再找另一臺機器),采用的方式是將本機的一個目錄"/home/src"作為“遠端”,掛載到本地的"/mnt/src"目錄。
sshfs localhost:/home/src /mnt/dst
掛載成功后,啟動應用層的fanotify監控進程(一個用于文件安全分析的listerner),然后在"/mnt/dst"目錄下進行一些文件操作,很快,系統就卡死了。
【排查】
這種本機掛載sshfs的方式理論上是沒有問題的(就像socket既可以用于網絡上不同主機間的通信,也可用于同一主機上不同進程間的通信),但終歸不是一種常規的使用方式。那用真正的“遠端”目錄來掛載下試試呢?
sshfs :/home/src /mnt/dst
接下來按照與先前同樣的步驟進行操作,問題沒有再出現。隱隱覺得是因為本機掛載的方式形成了一種循環,不過直覺歸直覺,總得找出確鑿的證據不是。
回到出現問題的“本機掛載”的方式,為了進一步縮小范圍,嘗試了幾種最簡單的文件操作,發現即便是在"/mnt/dst"目錄下通過"touch"命令創建一個文件,也可以讓問題100%地穩定復現。那我們就使用這個"touch"操作,結合一些調試手段,來探究下系統卡死的根本原因。
【分析】
首先使用最易用的"strace"工具來追蹤下"touch"命令的執行,跟預想的差不多,是在進行 ?open()系統調用的時候卡住的:
strace只能追蹤system call,要想知道open()之后到底發生了什么,得深入到內核里面去看看,這就要用到Linux自帶的ftrace工具了。把"fanotify"和"fsnotify"相關的函數加入待觀測的列表,抓取"touch"命令執行后的ftrace結果:
由于"fsnotify"的若干函數打印輸出過多,對主干的分析形成了一定的干擾,于是將下列三個函數的記錄排除在外:
調整之后可以看到,此過程所涉及的進程主要有"touch", "sshfs", "sftp-se"(即sftp server)和"listener"。
經過對ftrace所展現的交互流程的梳理,感覺離真相已經很接近了,但還是缺少了一些關鍵信息,沒有形成一個完整的邏輯鏈。不過,還剩一個終極手段沒有用,那就是在系統卡死的時候強制產生coredump,然后用crash工具來分析(前提是你有這個內核的debuginfo)。
一般強制觸發crash的方式是在終端輸入"echo c > /proc/sysrq-trigger",但在系統卡死的這種情景下,搶時間點完成這個輸入有一些困難,如果是在KVM的環境中,可以通過以下命令,導出虛擬機的內存轉儲:
virsh dump ?--memory-only ?--format=kdump-zlib
有了coredump和debuginfo,就可以開始hack了。從源頭入手,先來看下"touch"進程的back trace。很明顯,它對文件的open操作被fanotify劫持了,但還沒有等到listener的處理結果:
那為什么listener沒有返回結果呢?繼續看下listener進程中負責處理fanotify的線程的棧信息:
作為監控listener,它會去嘗試打開下這個文件,以判斷這個文件的內容是否安全,由于該文件屬于FUSE文件系統,所以需要將操作的請求上報給用戶態的daemon進程(即sshfs)。listener陷入等待之后,沒有收到sshfs的返回結果。
那到底是listener的fuse request沒有發出去呢,還是sshfs沒有做出respond呢?這可以通過查看該"fuse_req"的狀態來確定,要查看狀態的值就得知道它的內存地址,而在x64體系中,為了提高效率,函數的前面幾個參數通常使用寄存器傳遞,這就給直接從棧信息來獲取參數的地址帶來了困難。
因此,只能迂回一下,借助它的下級函數中保存的寄存器值來尋找和"fuse_req"的關聯。這里,"rbx", "r12"到"r15"都是潛在的突破口:
瀏覽wait_answer_interruptible()函數的源代碼,其中有兩處對"fuse_req"指針的引用:
void wait_answer_interruptible(struct fuse_conn *fc, struct fuse_req *req)
{
if (signal_pending(current))return;spin_unlock(&fc->lock);wait_event_interruptible(req->waitq, req->state == FUSE_REQ_FINISHED);spin_lock(&fc->lock);
}
總結
以上是生活随笔為你收集整理的中fuse_一个Fanotify和FUSE配合使用导致的问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python开发的模型部署_使用Pyth
- 下一篇: python类和对象介绍_python中