Hello,I'm working on a user-space file system implementation utilizing fuse kernel module (and libfuse user-space library). This file system implementation provides a custom ioctl operation that uses fuse_lowlevel_notify_inval_inode() function (which translates to fuse_notify_inval_inode() in kernel) to notify the kernel that the file was changed by the ioctl. However, under certain circumstances, fuse_notify_inval_inode() call is ineffective, resulting in incorrect file attributes being cached by the kernel. The problem occurs when ioctl() is executed in parallel with getattr().
I noticed this problem on RedHat 7.7 (3.10.0-1062.el7.x86_64), but I believe mainline kernel is affected as well.
I think there is a bug in the way fuse_notify_inval_inode() invalidates file attributes. If getattr() started before fuse_notify_inval_inode() was called, then the attributes returned by user-space process may be out of date, and should not be cached. But fuse_notify_inval_inode() only clears attribute validity time, which does not prevent getattr() result from being cached.
More precisely, this bug occurs in the following scenario: 1. kernel starts handling ioctl 2. file system process receives ioctl request 3. kernel starts handling getattr 4. file system process receives getattr request 5. file system process executes getattr 6. file system process executes ioctl, changing file state7. file system process invokes fuse_lowlevel_notify_inval_inode(), which invalidates file attributes in kernel
8. file system process sends ioctl reply, ioctl handling ends9. file system process sends getattr reply, attributes are incorrectly cached in the kernel
(Note that this scenario assumes that file system implementation is multi-threaded, therefore allowing ioctl() and getattr() to be handled in parallel.)
I'm attaching an archive with a reproduction test for this problem. The archive contains a README file explaining how to compile/run it.
By the way, to avoid a somehow similar race when write() is executed in parallel with getattr(), there is a special FUSE_I_SIZE_UNSTABLE bit set for the duration of write() operation.
Best regards, Krzysztof Rusek
Attachment:
fusebug.tar.gz
Description: application/gzip