一个Fanotify和FUSE配合使用导致的问题(一个手机号怎么注册两个微信)

2023-03-10 21:39:25

 

说明:本文所使用的内核版本为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"(原理是dereference一个空指针),但在系统卡死的这种情景下,抢时间点完成这个输入有一些困难,如果是在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"的状态来确定,要查看状态的值就得知道它的内存地址前面的文章介绍过,x86-64采用寄存器传参,难以直接从stack中直接获取寄存器的值,因此只能迂回一下,借助它的下级函数中保存的寄存器值来寻找和"fuse_req"的关联。

这里,"rbx", "r12"到"r15"都是潜在的突破口:

浏览wait_answer_interruptible()函数的源代码,其中有两处对"fuse_req"指针的引用:voidwait_answer_interruptible(structfuse_conn

*fc,structfuse_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);}指针的引用在汇编代码里对应着基址和以常数表示的偏移,也是分析中很好用的突破口。

在crash工具里获取结构体位域的offset值是很方便的,不过"fuse"是作为一个模块存在的,它的调试信息并不包含在vmlinux里,所以需要手动指定其debuginfo所在的路径并加载:

再次使用"whatis"命令查看,可以获知"state"和"waitq"在"fuse_req"结构体中的偏移分别是"0x34"和"0xe0":

结合上级函数调用schedule()之前的反汇编可以发现,"rbx"和"rsi"里都保存了我们要分析的这个"fuse_req"的地址,而"rbx"在接下来被push到栈之前,其值没有被更改,所以知道了栈上保存的"rbx"的值,也就知道了"fuse_req"的地址。

由于"rbx"是第5个被push到栈上的寄存器,可查到其值是"0xffff89ba78de2000":

好,接下来就可以读取这个"fuse_req"的状态,crash工具很贴心地将表示状态的值表示成了宏的形式:

这里可以进一步验证下是哪个进程在这个wait queue上等待。先获取一下"list_head"等待链表的地址:

然后根据起始地址进行搜索,crash工具就可帮我们查出,确实是"listener"进程:

除了上面介绍的这种方法,还可以直接用crash提供的"waitq"工具来查询等待进程的信息:

看来listener的fuse request确实发给sshfs了,是sshfs没有及时做出回应前面说过,在SFTP协议中,sshfs作为客户端,它需要将本地创建文件的操作告知sftp server那"sftp-server"在系统卡死的时候是个什么状态呢?。

嗯,既然本地的挂载目录下都"touch"了一个文件,那作为“远端”,也会同步创建一个文件,然而这个“远端”是在本机上,自然也逃不过"fanotify"的劫持它在等待listener返回结果,而listener又在等待sshfs返回结果……。

通常情况下,listener在打开一个监听的文件后,可以很快做出回应,不存在向fuse daemon上报的情况,因此从实现上,listener进程中接收fanotify事件和返回结果是在同一线程中处理的,而在fuse.sshfs本机挂载的场景下,就会形成这样一个死循环:

为什么之前采用ftrace没有得到这样一个完备的链路呢?从刚才的crash栈回溯来看,可能是因为漏掉了对"fuse"相关函数的追踪那把这些函数加到ftrace filter中,再来复现抓取一次梳理这次的ftrace输出结果,就非常清晰了,最后差的那1%的证据终于被补齐了。

由于"touch"命令涉及多个系统调用,因此在问题出现前,这几个关键进程间一共有4轮交互前面的3轮都能够顺利返回,但到了第4轮,真正申请inode,在磁盘上分配文件空间时,就出现了一条单向的不归路……参考:

Fsnotify and FUSE原创文章,转载请注明出处。


以上就是关于《一个Fanotify和FUSE配合使用导致的问题(一个手机号怎么注册两个微信)》的全部内容,本文网址:https://www.7ca.cn/baike/3524.shtml,如对您有帮助可以分享给好友,谢谢。
标签:
声明

排行榜