]> www.infradead.org Git - users/willy/pagecache.git/commitdiff
fuse: don't truncate cached, mutated symlink
authorMiklos Szeredi <mszeredi@redhat.com>
Thu, 20 Feb 2025 10:02:58 +0000 (11:02 +0100)
committerChristian Brauner <brauner@kernel.org>
Thu, 20 Feb 2025 14:48:17 +0000 (15:48 +0100)
Fuse allows the value of a symlink to change and this property is exploited
by some filesystems (e.g. CVMFS).

It has been observed, that sometimes after changing the symlink contents,
the value is truncated to the old size.

This is caused by fuse_getattr() racing with fuse_reverse_inval_inode().
fuse_reverse_inval_inode() updates the fuse_inode's attr_version, which
results in fuse_change_attributes() exiting before updating the cached
attributes

This is okay, as the cached attributes remain invalid and the next call to
fuse_change_attributes() will likely update the inode with the correct
values.

The reason this causes problems is that cached symlinks will be
returned through page_get_link(), which truncates the symlink to
inode->i_size.  This is correct for filesystems that don't mutate
symlinks, but in this case it causes bad behavior.

The solution is to just remove this truncation.  This can cause a
regression in a filesystem that relies on supplying a symlink larger than
the file size, but this is unlikely.  If that happens we'd need to make
this behavior conditional.

Reported-by: Laura Promberger <laura.promberger@cern.ch>
Tested-by: Sam Lewis <samclewis@google.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Link: https://lore.kernel.org/r/20250220100258.793363-1-mszeredi@redhat.com
Reviewed-by: Bernd Schubert <bschubert@ddn.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/fuse/dir.c
fs/namei.c
include/linux/fs.h

index 198862b086ff7bad4007ec2f3200377d12a78385..3805f9b06c9d2df962d786705620102b8de7fa12 100644 (file)
@@ -1636,7 +1636,7 @@ static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
                goto out_err;
 
        if (fc->cache_symlinks)
-               return page_get_link(dentry, inode, callback);
+               return page_get_link_raw(dentry, inode, callback);
 
        err = -ECHILD;
        if (!dentry)
index 3ab9440c5b9313bc4751ab97a1ae73194409188a..ecb7b95c2ca338c0e03d335104b39e2fce01c6ca 100644 (file)
@@ -5356,10 +5356,9 @@ const char *vfs_get_link(struct dentry *dentry, struct delayed_call *done)
 EXPORT_SYMBOL(vfs_get_link);
 
 /* get the link contents into pagecache */
-const char *page_get_link(struct dentry *dentry, struct inode *inode,
-                         struct delayed_call *callback)
+static char *__page_get_link(struct dentry *dentry, struct inode *inode,
+                            struct delayed_call *callback)
 {
-       char *kaddr;
        struct page *page;
        struct address_space *mapping = inode->i_mapping;
 
@@ -5378,8 +5377,23 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode,
        }
        set_delayed_call(callback, page_put_link, page);
        BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
-       kaddr = page_address(page);
-       nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
+       return page_address(page);
+}
+
+const char *page_get_link_raw(struct dentry *dentry, struct inode *inode,
+                             struct delayed_call *callback)
+{
+       return __page_get_link(dentry, inode, callback);
+}
+EXPORT_SYMBOL_GPL(page_get_link_raw);
+
+const char *page_get_link(struct dentry *dentry, struct inode *inode,
+                                       struct delayed_call *callback)
+{
+       char *kaddr = __page_get_link(dentry, inode, callback);
+
+       if (!IS_ERR(kaddr))
+               nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
        return kaddr;
 }
 
index 2c3b2f8a621f76a08706df94c2b90540f7369568..9346adf28f7bc4724d92b1c903272b841f8ed264 100644 (file)
@@ -3452,6 +3452,8 @@ extern const struct file_operations generic_ro_fops;
 
 extern int readlink_copy(char __user *, int, const char *, int);
 extern int page_readlink(struct dentry *, char __user *, int);
+extern const char *page_get_link_raw(struct dentry *, struct inode *,
+                                    struct delayed_call *);
 extern const char *page_get_link(struct dentry *, struct inode *,
                                 struct delayed_call *);
 extern void page_put_link(void *);