The attached fix needs folding in across this patch (23), patch 24, patch 29 and patch 31. David --- commit 9d4429bc7bb3f2b518d6decd1ca0e99e4d80d58e Author: David Howells <dhowells@xxxxxxxxxx> Date: Thu Nov 7 23:46:48 2024 +0000 afs: Fix handling of signals during readdir When a directory is being read, whether or not the dvnode->directory buffer pointer is NULL is used to track whether we've checked fscache yet. However, if a signal occurs after the buffer being allocated but whilst we're doing the read, we may end up in an invalid state with ->directory set but no data in the buffer. In this state, afs_readdir(), afs_lookup() and afs_d_revalidate() see corrupt directory contents leading to a variety of malfunctions. Fix this by providing a specific flag to record whether or not we've performed a read yet - and, incidentally, sampled fscache - rather than using the value in ->directory instead. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> cc: Marc Dionne <marc.dionne@xxxxxxxxxxxx> cc: linux-afs@xxxxxxxxxxxxxxxxxxx diff --git a/fs/afs/dir.c b/fs/afs/dir.c index 663a212964d8..b6a202fd9926 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c @@ -323,7 +323,7 @@ ssize_t afs_read_dir(struct afs_vnode *dvnode, struct file *file) * haven't read it yet. */ if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && - dvnode->directory) { + test_bit(AFS_VNODE_DIR_READ, &dvnode->flags)) { ret = i_size; goto valid; } @@ -336,7 +336,7 @@ ssize_t afs_read_dir(struct afs_vnode *dvnode, struct file *file) afs_invalidate_cache(dvnode, 0); if (!test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) || - !dvnode->directory) { + !test_bit(AFS_VNODE_DIR_READ, &dvnode->flags)) { trace_afs_reload_dir(dvnode); ret = afs_read_single(dvnode, file); if (ret < 0) @@ -345,6 +345,7 @@ ssize_t afs_read_dir(struct afs_vnode *dvnode, struct file *file) // TODO: Trim excess pages set_bit(AFS_VNODE_DIR_VALID, &dvnode->flags); + set_bit(AFS_VNODE_DIR_READ, &dvnode->flags); } else { ret = i_size; } diff --git a/fs/afs/dir_edit.c b/fs/afs/dir_edit.c index f6f4b1adc8dc..60a549f1d9c5 100644 --- a/fs/afs/dir_edit.c +++ b/fs/afs/dir_edit.c @@ -644,4 +644,5 @@ void afs_mkdir_init_dir(struct afs_vnode *dvnode, struct afs_vnode *parent_dvnod netfs_single_mark_inode_dirty(&dvnode->netfs.inode); set_bit(AFS_VNODE_DIR_VALID, &dvnode->flags); + set_bit(AFS_VNODE_DIR_READ, &dvnode->flags); } diff --git a/fs/afs/inode.c b/fs/afs/inode.c index f5618564b3fc..e9538e91f848 100644 --- a/fs/afs/inode.c +++ b/fs/afs/inode.c @@ -39,6 +39,7 @@ void afs_init_new_symlink(struct afs_vnode *vnode, struct afs_operation *op) p = kmap_local_folio(folioq_folio(vnode->directory, 0), 0); memcpy(p, op->create.symlink, size); kunmap_local(p); + set_bit(AFS_VNODE_DIR_READ, &vnode->flags); netfs_single_mark_inode_dirty(&vnode->netfs.inode); } @@ -60,12 +61,12 @@ const char *afs_get_link(struct dentry *dentry, struct inode *inode, if (!dentry) { /* RCU pathwalk. */ - if (!vnode->directory || !afs_check_validity(vnode)) + if (!test_bit(AFS_VNODE_DIR_READ, &vnode->flags) || !afs_check_validity(vnode)) return ERR_PTR(-ECHILD); goto good; } - if (!vnode->directory) + if (test_bit(AFS_VNODE_DIR_READ, &vnode->flags)) goto fetch; ret = afs_validate(vnode, NULL); @@ -73,13 +74,14 @@ const char *afs_get_link(struct dentry *dentry, struct inode *inode, return ERR_PTR(ret); if (!test_and_clear_bit(AFS_VNODE_ZAP_DATA, &vnode->flags) && - vnode->directory) + test_bit(AFS_VNODE_DIR_READ, &vnode->flags)) goto good; fetch: ret = afs_read_single(vnode, NULL); if (ret < 0) return ERR_PTR(ret); + set_bit(AFS_VNODE_DIR_READ, &vnode->flags); good: folio = folioq_folio(vnode->directory, 0); diff --git a/fs/afs/internal.h b/fs/afs/internal.h index a5da0dd8e9cc..90f407774a9a 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h @@ -705,6 +705,7 @@ struct afs_vnode { #define AFS_VNODE_NEW_CONTENT 8 /* Set if file has new content (create/trunc-0) */ #define AFS_VNODE_SILLY_DELETED 9 /* Set if file has been silly-deleted */ #define AFS_VNODE_MODIFYING 10 /* Set if we're performing a modification op */ +#define AFS_VNODE_DIR_READ 11 /* Set if we've read a dir's contents */ struct folio_queue *directory; /* Directory contents */ struct list_head wb_keys; /* List of keys available for writeback */