Re: [PATCH] fs/fuse: fix race between concurrent setattr from multiple nodes

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 4/10/25 10:21, Guang Yuan Wu wrote:
> 
> Regards
> Guang Yuan Wu
> 
> ________________________________________
> From: Bernd Schubert
> Sent: Thursday, April 10, 2025 6:18 AM
> To: Guang Yuan Wu; linux-fsdevel@xxxxxxxxxxxxxxx
> Cc: mszeredi@xxxxxxxxxx
> Subject: Re: [PATCH] fs/fuse: fix race between concurrent setattr from multiple nodes
> 
> 
> On 4/9/25 16:25, Guang Yuan Wu wrote:
>>  fuse: fix race between concurrent setattrs from multiple nodes
>>
>>     When mounting a user-space filesystem on multiple clients, after
>>     concurrent ->setattr() calls from different node, stale inode attributes
>>     may be cached in some node.
>>
>>     This is caused by fuse_setattr() racing with fuse_reverse_inval_inode().
>>
>>     When filesystem server receives setattr request, the client node with
>>     valid iattr cached will be required to update the fuse_inode's attr_version
>>     and invalidate the cache by fuse_reverse_inval_inode(), and at the next
>>     call to ->getattr() they will be fetched from user-space.
>>
>>     The race scenario is:
>>       1. client-1 sends setattr (iattr-1) request to server
>>       2. client-1 receives the reply from server
>>       3. before client-1 updates iattr-1 to the cached attributes by
>>          fuse_change_attributes_common(), server receives another setattr
>>          (iattr-2) request from client-2
>>       4. server requests client-1 to update the inode attr_version and
>>          invalidate the cached iattr, and iattr-1 becomes staled
>>       5. client-2 receives the reply from server, and caches iattr-2
>>       6. continue with step 2, client-1 invokes fuse_change_attributes_common(),
>>          and caches iattr-1
>>
>>     The issue has been observed from concurrent of chmod, chown, or truncate,
>>     which all invoke ->setattr() call.
>>
>>     The solution is to use fuse_inode's attr_version to check whether the
>>     attributes have been modified during the setattr request's lifetime. If so,
>>     mark the attributes as stale after fuse_change_attributes_common().
>>
>>     Signed-off-by: Guang Yuan Wu <gwu@xxxxxxx>
>> ---
>>  fs/fuse/dir.c | 12 ++++++++++++
>>  1 file changed, 12 insertions(+)
>>
>> diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
>> index d58f96d1e9a2..df3a6c995dc6 100644
>> --- a/fs/fuse/dir.c
>> +++ b/fs/fuse/dir.c
>> @@ -1889,6 +1889,8 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
>>         int err;
>>         bool trust_local_cmtime = is_wb;
>>         bool fault_blocked = false;
>> +       bool invalidate_attr = false;
>> +       u64 attr_version;
>>
>>         if (!fc->default_permissions)
>>                 attr->ia_valid |= ATTR_FORCE;
>> @@ -1973,6 +1975,8 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
>>                 if (fc->handle_killpriv_v2 && !capable(CAP_FSETID))
>>                         inarg.valid |= FATTR_KILL_SUIDGID;
>>         }
>> +
>> +       attr_version = fuse_get_attr_version(fm->fc);
>>         fuse_setattr_fill(fc, &args, inode, &inarg, &outarg);
>>         err = fuse_simple_request(fm, &args);
>>         if (err) {
>> @@ -1998,9 +2002,17 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
>>                 /* FIXME: clear I_DIRTY_SYNC? */
>>         }
>>
>> +       if ((attr_version != 0 && fi->attr_version > attr_version) ||
>> +               test_bit(FUSE_I_SIZE_UNSTABLE, &fi->state))
>> +               invalidate_attr = true;
>> +
>>         fuse_change_attributes_common(inode, &outarg.attr, NULL,
>>                                       ATTR_TIMEOUT(&outarg),
>>                                       fuse_get_cache_mask(inode), 0);
>> +
>> +       if (invalidate_attr)
>> +               fuse_invalidate_attr(inode);
> 
> Thank you, I think the idea is right. Just some questions.
> I wonder if we need to set attributes at all, when just invaliding
> them directly after? fuse_change_attributes_i() is just bailing out then?
> Also, do we need to test for FUSE_I_SIZE_UNSTABLE here (truncate related,
> I think) or is just testing for the attribute version enough.

<moved comments inline>

> Hi Bernd,
> I think in such case, although outarg.attr (reply from server) is staled, 
> but it is still valid attr (pass above fuse_invalid_attr() check).
> set it and then mark it as stale, is for fsnotify_change() consideration
> after ->setattr() returns, new attr value may be used and could cause
> potential issue if not set it before ->setattr() returns. Sure, the value
> may be staled and this should be checked by caller.
> 
> Later, iattr data will be revalidated from the next ->getattr() call.

Good point about fsnotify_change(), would you mind to add a comment about
that?

+       if ((attr_version != 0 && fi->attr_version > attr_version) ||
+               test_bit(FUSE_I_SIZE_UNSTABLE, &fi->state)) {
+		/* Applying attributes, for example for fsnotify_change() */
+               invalidate_attr = true;


> 
> I am unclear why FUSE_I_SIZE_UNSTABLE should be checked here, can you
> provide more detail ? Thanks.


The function itself is setting it on truncate - we can trust attributes
in that case. I think if we want to test for FUSE_I_SIZE_UNSTABLE, it 
should check for 

if ((attr_version != 0 && fi->attr_version > attr_version) ||
    (test_bit(FUSE_I_SIZE_UNSTABLE, &fi->state)) && !truncate))


Thanks,
Bernd




[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [NTFS 3]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [NTFS 3]     [Samba]     [Device Mapper]     [CEPH Development]

  Powered by Linux