Skip to content

Commit b4c173d

Browse files
Miklos Szeredibrauner
authored andcommitted
fuse: don't truncate cached, mutated symlink
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 <[email protected]> Tested-by: Sam Lewis <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]> Link: https://lore.kernel.org/r/[email protected] Reviewed-by: Bernd Schubert <[email protected]> Signed-off-by: Christian Brauner <[email protected]>
1 parent c84e125 commit b4c173d

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

fs/fuse/dir.c

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

16381638
if (fc->cache_symlinks)
1639-
return page_get_link(dentry, inode, callback);
1639+
return page_get_link_raw(dentry, inode, callback);
16401640

16411641
err = -ECHILD;
16421642
if (!dentry)

fs/namei.c

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

53585358
/* get the link contents into pagecache */
5359-
const char *page_get_link(struct dentry *dentry, struct inode *inode,
5360-
struct delayed_call *callback)
5359+
static char *__page_get_link(struct dentry *dentry, struct inode *inode,
5360+
struct delayed_call *callback)
53615361
{
5362-
char *kaddr;
53635362
struct page *page;
53645363
struct address_space *mapping = inode->i_mapping;
53655364

@@ -5378,8 +5377,23 @@ const char *page_get_link(struct dentry *dentry, struct inode *inode,
53785377
}
53795378
set_delayed_call(callback, page_put_link, page);
53805379
BUG_ON(mapping_gfp_mask(mapping) & __GFP_HIGHMEM);
5381-
kaddr = page_address(page);
5382-
nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
5380+
return page_address(page);
5381+
}
5382+
5383+
const char *page_get_link_raw(struct dentry *dentry, struct inode *inode,
5384+
struct delayed_call *callback)
5385+
{
5386+
return __page_get_link(dentry, inode, callback);
5387+
}
5388+
EXPORT_SYMBOL_GPL(page_get_link_raw);
5389+
5390+
const char *page_get_link(struct dentry *dentry, struct inode *inode,
5391+
struct delayed_call *callback)
5392+
{
5393+
char *kaddr = __page_get_link(dentry, inode, callback);
5394+
5395+
if (!IS_ERR(kaddr))
5396+
nd_terminate_link(kaddr, inode->i_size, PAGE_SIZE - 1);
53835397
return kaddr;
53845398
}
53855399

include/linux/fs.h

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

34533453
extern int readlink_copy(char __user *, int, const char *, int);
34543454
extern int page_readlink(struct dentry *, char __user *, int);
3455+
extern const char *page_get_link_raw(struct dentry *, struct inode *,
3456+
struct delayed_call *);
34553457
extern const char *page_get_link(struct dentry *, struct inode *,
34563458
struct delayed_call *);
34573459
extern void page_put_link(void *);

0 commit comments

Comments
 (0)