Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfs: Fix i_size updating

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>

authored by

David Howells and committed by
Christian Brauner
2e065894 74ee76be

+8 -2
+2
fs/netfs/buffered_write.c
··· 64 64 return; 65 65 } 66 66 67 + spin_lock(&inode->i_lock); 67 68 i_size_write(inode, pos); 68 69 #if IS_ENABLED(CONFIG_FSCACHE) 69 70 fscache_update_cookie(ctx->cache, NULL, &pos); ··· 78 77 DIV_ROUND_UP(pos, SECTOR_SIZE), 79 78 inode->i_blocks + add); 80 79 } 80 + spin_unlock(&inode->i_lock); 81 81 } 82 82 83 83 /**
+6 -2
fs/netfs/direct_write.c
··· 14 14 struct inode *inode = wreq->inode; 15 15 unsigned long long end = wreq->start + wreq->transferred; 16 16 17 - if (!wreq->error && 18 - i_size_read(inode) < end) { 17 + if (wreq->error || end <= i_size_read(inode)) 18 + return; 19 + 20 + spin_lock(&inode->i_lock); 21 + if (end > i_size_read(inode)) { 19 22 if (wreq->netfs_ops->update_i_size) 20 23 wreq->netfs_ops->update_i_size(inode, end); 21 24 else 22 25 i_size_write(inode, end); 23 26 } 27 + spin_unlock(&inode->i_lock); 24 28 } 25 29 26 30 /*