netfs: Fix i_size updating
authorDavid Howells <dhowells@redhat.com>
Tue, 1 Jul 2025 16:38:45 +0000 (17:38 +0100)
committerChristian Brauner <brauner@kernel.org>
Tue, 1 Jul 2025 20:37:14 +0000 (22:37 +0200)
Fix the updating of i_size, particularly in regard to the completion of DIO
writes and especially async DIO writes by using a lock.

The bug is triggered occasionally by the generic/207 xfstest as it chucks a
bunch of AIO DIO writes at the filesystem and then checks that fstat()
returns a reasonable st_size as each completes.

The problem is that netfs is trying to do "if new_size > inode->i_size,
update inode->i_size" sort of thing but without a lock around it.

This can be seen with cifs, but shouldn't be seen with kafs because kafs
serialises modification ops on the client whereas cifs sends the requests
to the server as they're generated and lets the server order them.

Fixes: 153a9961b551 ("netfs: Implement unbuffered/DIO write support")
Signed-off-by: David Howells <dhowells@redhat.com>
Link: https://lore.kernel.org/20250701163852.2171681-11-dhowells@redhat.com
Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
cc: Steve French <sfrench@samba.org>
cc: Paulo Alcantara <pc@manguebit.org>
cc: linux-cifs@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/netfs/buffered_write.c
fs/netfs/direct_write.c

index 72a3e6db25248b3b73385295f06801b3c3648d07..b87ef3fe4ea486bd8feeaf7f1aef346deb2cd3f8 100644 (file)
@@ -64,6 +64,7 @@ static void netfs_update_i_size(struct netfs_inode *ctx, struct inode *inode,
                return;
        }
 
+       spin_lock(&inode->i_lock);
        i_size_write(inode, pos);
 #if IS_ENABLED(CONFIG_FSCACHE)
        fscache_update_cookie(ctx->cache, NULL, &pos);
@@ -77,6 +78,7 @@ static void netfs_update_i_size(struct netfs_inode *ctx, struct inode *inode,
                                        DIV_ROUND_UP(pos, SECTOR_SIZE),
                                        inode->i_blocks + add);
        }
+       spin_unlock(&inode->i_lock);
 }
 
 /**
index fa9a5bf3c6d512d905178fb6c920f538b1f6ee73..3efa5894b2c07d88763989527fa1aea24610f7e7 100644 (file)
@@ -14,13 +14,17 @@ static void netfs_cleanup_dio_write(struct netfs_io_request *wreq)
        struct inode *inode = wreq->inode;
        unsigned long long end = wreq->start + wreq->transferred;
 
-       if (!wreq->error &&
-           i_size_read(inode) < end) {
+       if (wreq->error || end <= i_size_read(inode))
+               return;
+
+       spin_lock(&inode->i_lock);
+       if (end > i_size_read(inode)) {
                if (wreq->netfs_ops->update_i_size)
                        wreq->netfs_ops->update_i_size(inode, end);
                else
                        i_size_write(inode, end);
        }
+       spin_unlock(&inode->i_lock);
 }
 
 /*