Skip to content

Commit ab28a32

Browse files
committed
fuse: don't truncate cached, mutated symlink
jira SECO-478 RFBugFix: FUSE commit-author Miklos Szeredi <mszeredi@redhat.com> commit b4c173d 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> (cherry picked from commit b4c173d) Signed-off-by: Jonathan Maple <jmaple@ciq.com>
1 parent a1c1968 commit ab28a32

3 files changed

Lines changed: 22 additions & 6 deletions

File tree

fs/fuse/dir.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1632,7 +1632,7 @@ static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
16321632
goto out_err;
16331633

16341634
if (fc->cache_symlinks)
1635-
return page_get_link(dentry, inode, callback);
1635+
return page_get_link_raw(dentry, inode, callback);
16361636

16371637
err = -ECHILD;
16381638
if (!dentry)

fs/namei.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5334,10 +5334,9 @@ const char *vfs_get_link(struct dentry *dentry, struct delayed_call *done)
53345334
EXPORT_SYMBOL(vfs_get_link);
53355335

53365336
/* get the link contents into pagecache */
5337-
const char *page_get_link(struct dentry *dentry, struct inode *inode,
5338-
struct delayed_call *callback)
5337+
static char *__page_get_link(struct dentry *dentry, struct inode *inode,
5338+
struct delayed_call *callback)
53395339
{
5340-
char *kaddr;
53415340
struct page *page;
53425341
struct address_space *mapping = inode->i_mapping;
53435342

@@ -5356,8 +5355,23 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode,
53565355
}
53575356
set_delayed_call(callback, page_put_link, page);
53585357
BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
5359-
kaddr = page_address(page);
5360-
nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
5358+
return page_address(page);
5359+
}
5360+
5361+
const char *page_get_link_raw(struct dentry *dentry, struct inode *inode,
5362+
struct delayed_call *callback)
5363+
{
5364+
return __page_get_link(dentry, inode, callback);
5365+
}
5366+
EXPORT_SYMBOL_GPL(page_get_link_raw);
5367+
5368+
const char *page_get_link(struct dentry *dentry, struct inode *inode,
5369+
struct delayed_call *callback)
5370+
{
5371+
char *kaddr = __page_get_link(dentry, inode, callback);
5372+
5373+
if (!IS_ERR(kaddr))
5374+
nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
53615375
return kaddr;
53625376
}
53635377

include/linux/fs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3389,6 +3389,8 @@ extern const struct file_operations generic_ro_fops;
33893389

33903390
extern int readlink_copy(char __user *, int, const char *, int);
33913391
extern int page_readlink(struct dentry *, char __user *, int);
3392+
extern const char *page_get_link_raw(struct dentry *, struct inode *,
3393+
struct delayed_call *);
33923394
extern const char *page_get_link(struct dentry *, struct inode *,
33933395
struct delayed_call *);
33943396
extern void page_put_link(void *);

0 commit comments

Comments
 (0)