Skip to content

Commit 69eb56f

Browse files
cntianciMiklos Szeredi
authored andcommitted
fuse: check attributes staleness on fuse_iget()
Function fuse_direntplus_link() might call fuse_iget() to initialize a new fuse_inode and change its attributes. If fi->attr_version is always initialized with 0, even if the attributes returned by the FUSE_READDIR request is staled, as the new fi->attr_version is 0, fuse_change_attributes will still set the staled attributes to inode. This wrong behaviour may cause file size inconsistency even when there is no changes from server-side. To reproduce the issue, consider the following 2 programs (A and B) are running concurrently, A B ---------------------------------- -------------------------------- { /fusemnt/dir/f is a file path in a fuse mount, the size of f is 0. } readdir(/fusemnt/dir) start //Daemon set size 0 to f direntry fallocate(f, 1024) stat(f) // B see size 1024 echo 2 > /proc/sys/vm/drop_caches readdir(/fusemnt/dir) reply to kernel Kernel set 0 to the I_NEW inode stat(f) // B see size 0 In the above case, only program B is modifying the file size, however, B observes file size changing between the 2 'readonly' stat() calls. To fix this issue, we should make sure readdirplus still follows the rule of attr_version staleness checking even if the fi->attr_version is lost due to inode eviction. To identify this situation, the new fc->evict_ctr is used to record whether the eviction of inodes occurs during the readdirplus request processing. If it does, the result of readdirplus may be inaccurate; otherwise, the result of readdirplus can be trusted. Although this may still lead to incorrect invalidation, considering the relatively low frequency of evict occurrences, it should be acceptable. Link: https://lore.kernel.org/lkml/[email protected]/ Link: https://lore.kernel.org/lkml/[email protected]/ Reported-by: Jiachen Zhang <[email protected]> Suggested-by: Miklos Szeredi <[email protected]> Signed-off-by: Zhang Tianci <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]>
1 parent 68bfb7e commit 69eb56f

File tree

4 files changed

+71
-25
lines changed

4 files changed

+71
-25
lines changed

fs/fuse/dir.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
366366
struct fuse_mount *fm = get_fuse_mount_super(sb);
367367
FUSE_ARGS(args);
368368
struct fuse_forget_link *forget;
369-
u64 attr_version;
369+
u64 attr_version, evict_ctr;
370370
int err;
371371

372372
*inode = NULL;
@@ -381,6 +381,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
381381
goto out;
382382

383383
attr_version = fuse_get_attr_version(fm->fc);
384+
evict_ctr = fuse_get_evict_ctr(fm->fc);
384385

385386
fuse_lookup_init(fm->fc, &args, nodeid, name, outarg);
386387
err = fuse_simple_request(fm, &args);
@@ -398,7 +399,7 @@ int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name
398399

399400
*inode = fuse_iget(sb, outarg->nodeid, outarg->generation,
400401
&outarg->attr, ATTR_TIMEOUT(outarg),
401-
attr_version);
402+
attr_version, evict_ctr);
402403
err = -ENOMEM;
403404
if (!*inode) {
404405
fuse_queue_forget(fm->fc, forget, outarg->nodeid, 1);
@@ -691,7 +692,7 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
691692
ff->nodeid = outentry.nodeid;
692693
ff->open_flags = outopenp->open_flags;
693694
inode = fuse_iget(dir->i_sb, outentry.nodeid, outentry.generation,
694-
&outentry.attr, ATTR_TIMEOUT(&outentry), 0);
695+
&outentry.attr, ATTR_TIMEOUT(&outentry), 0, 0);
695696
if (!inode) {
696697
flags &= ~(O_CREAT | O_EXCL | O_TRUNC);
697698
fuse_sync_release(NULL, ff, flags);
@@ -822,7 +823,7 @@ static int create_new_entry(struct mnt_idmap *idmap, struct fuse_mount *fm,
822823
goto out_put_forget_req;
823824

824825
inode = fuse_iget(dir->i_sb, outarg.nodeid, outarg.generation,
825-
&outarg.attr, ATTR_TIMEOUT(&outarg), 0);
826+
&outarg.attr, ATTR_TIMEOUT(&outarg), 0, 0);
826827
if (!inode) {
827828
fuse_queue_forget(fm->fc, forget, outarg.nodeid, 1);
828829
return -ENOMEM;
@@ -2028,7 +2029,7 @@ int fuse_do_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
20282029

20292030
fuse_change_attributes_common(inode, &outarg.attr, NULL,
20302031
ATTR_TIMEOUT(&outarg),
2031-
fuse_get_cache_mask(inode));
2032+
fuse_get_cache_mask(inode), 0);
20322033
oldsize = inode->i_size;
20332034
/* see the comment in fuse_change_attributes() */
20342035
if (!is_wb || is_truncate)

fs/fuse/fuse_i.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,9 @@ struct fuse_conn {
890890
/** Version counter for attribute changes */
891891
atomic64_t attr_version;
892892

893+
/** Version counter for evict inode */
894+
atomic64_t evict_ctr;
895+
893896
/** Called on final put */
894897
void (*release)(struct fuse_conn *);
895898

@@ -984,6 +987,11 @@ static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
984987
return atomic64_read(&fc->attr_version);
985988
}
986989

990+
static inline u64 fuse_get_evict_ctr(struct fuse_conn *fc)
991+
{
992+
return atomic64_read(&fc->evict_ctr);
993+
}
994+
987995
static inline bool fuse_stale_inode(const struct inode *inode, int generation,
988996
struct fuse_attr *attr)
989997
{
@@ -1043,7 +1051,8 @@ extern const struct dentry_operations fuse_root_dentry_operations;
10431051
*/
10441052
struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
10451053
int generation, struct fuse_attr *attr,
1046-
u64 attr_valid, u64 attr_version);
1054+
u64 attr_valid, u64 attr_version,
1055+
u64 evict_ctr);
10471056

10481057
int fuse_lookup_name(struct super_block *sb, u64 nodeid, const struct qstr *name,
10491058
struct fuse_entry_out *outarg, struct inode **inode);
@@ -1133,7 +1142,8 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr,
11331142

11341143
void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
11351144
struct fuse_statx *sx,
1136-
u64 attr_valid, u32 cache_mask);
1145+
u64 attr_valid, u32 cache_mask,
1146+
u64 evict_ctr);
11371147

11381148
u32 fuse_get_cache_mask(struct inode *inode);
11391149

fs/fuse/inode.c

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ static void fuse_evict_inode(struct inode *inode)
175175
fuse_cleanup_submount_lookup(fc, fi->submount_lookup);
176176
fi->submount_lookup = NULL;
177177
}
178+
/*
179+
* Evict of non-deleted inode may race with outstanding
180+
* LOOKUP/READDIRPLUS requests and result in inconsistency when
181+
* the request finishes. Deal with that here by bumping a
182+
* counter that can be compared to the starting value.
183+
*/
184+
if (inode->i_nlink > 0)
185+
atomic64_inc(&fc->evict_ctr);
178186
}
179187
if (S_ISREG(inode->i_mode) && !fuse_is_bad(inode)) {
180188
WARN_ON(fi->iocachectr != 0);
@@ -208,17 +216,30 @@ static ino_t fuse_squash_ino(u64 ino64)
208216

209217
void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr,
210218
struct fuse_statx *sx,
211-
u64 attr_valid, u32 cache_mask)
219+
u64 attr_valid, u32 cache_mask,
220+
u64 evict_ctr)
212221
{
213222
struct fuse_conn *fc = get_fuse_conn(inode);
214223
struct fuse_inode *fi = get_fuse_inode(inode);
215224

216225
lockdep_assert_held(&fi->lock);
217226

227+
/*
228+
* Clear basic stats from invalid mask.
229+
*
230+
* Don't do this if this is coming from a fuse_iget() call and there
231+
* might have been a racing evict which would've invalidated the result
232+
* if the attr_version would've been preserved.
233+
*
234+
* !evict_ctr -> this is create
235+
* fi->attr_version != 0 -> this is not a new inode
236+
* evict_ctr == fuse_get_evict_ctr() -> no evicts while during request
237+
*/
238+
if (!evict_ctr || fi->attr_version || evict_ctr == fuse_get_evict_ctr(fc))
239+
set_mask_bits(&fi->inval_mask, STATX_BASIC_STATS, 0);
240+
218241
fi->attr_version = atomic64_inc_return(&fc->attr_version);
219242
fi->i_time = attr_valid;
220-
/* Clear basic stats from invalid mask */
221-
set_mask_bits(&fi->inval_mask, STATX_BASIC_STATS, 0);
222243

223244
inode->i_ino = fuse_squash_ino(attr->ino);
224245
inode->i_mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777);
@@ -297,9 +318,9 @@ u32 fuse_get_cache_mask(struct inode *inode)
297318
return STATX_MTIME | STATX_CTIME | STATX_SIZE;
298319
}
299320

300-
void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr,
301-
struct fuse_statx *sx,
302-
u64 attr_valid, u64 attr_version)
321+
static void fuse_change_attributes_i(struct inode *inode, struct fuse_attr *attr,
322+
struct fuse_statx *sx, u64 attr_valid,
323+
u64 attr_version, u64 evict_ctr)
303324
{
304325
struct fuse_conn *fc = get_fuse_conn(inode);
305326
struct fuse_inode *fi = get_fuse_inode(inode);
@@ -333,7 +354,8 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr,
333354
}
334355

335356
old_mtime = inode_get_mtime(inode);
336-
fuse_change_attributes_common(inode, attr, sx, attr_valid, cache_mask);
357+
fuse_change_attributes_common(inode, attr, sx, attr_valid, cache_mask,
358+
evict_ctr);
337359

338360
oldsize = inode->i_size;
339361
/*
@@ -374,6 +396,13 @@ void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr,
374396
fuse_dax_dontcache(inode, attr->flags);
375397
}
376398

399+
void fuse_change_attributes(struct inode *inode, struct fuse_attr *attr,
400+
struct fuse_statx *sx, u64 attr_valid,
401+
u64 attr_version)
402+
{
403+
fuse_change_attributes_i(inode, attr, sx, attr_valid, attr_version, 0);
404+
}
405+
377406
static void fuse_init_submount_lookup(struct fuse_submount_lookup *sl,
378407
u64 nodeid)
379408
{
@@ -428,7 +457,8 @@ static int fuse_inode_set(struct inode *inode, void *_nodeidp)
428457

429458
struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
430459
int generation, struct fuse_attr *attr,
431-
u64 attr_valid, u64 attr_version)
460+
u64 attr_valid, u64 attr_version,
461+
u64 evict_ctr)
432462
{
433463
struct inode *inode;
434464
struct fuse_inode *fi;
@@ -489,8 +519,8 @@ struct inode *fuse_iget(struct super_block *sb, u64 nodeid,
489519
fi->nlookup++;
490520
spin_unlock(&fi->lock);
491521
done:
492-
fuse_change_attributes(inode, attr, NULL, attr_valid, attr_version);
493-
522+
fuse_change_attributes_i(inode, attr, NULL, attr_valid, attr_version,
523+
evict_ctr);
494524
return inode;
495525
}
496526

@@ -942,6 +972,7 @@ void fuse_conn_init(struct fuse_conn *fc, struct fuse_mount *fm,
942972
fc->initialized = 0;
943973
fc->connected = 1;
944974
atomic64_set(&fc->attr_version, 1);
975+
atomic64_set(&fc->evict_ctr, 1);
945976
get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key));
946977
fc->pid_ns = get_pid_ns(task_active_pid_ns(current));
947978
fc->user_ns = get_user_ns(user_ns);
@@ -1003,7 +1034,7 @@ static struct inode *fuse_get_root_inode(struct super_block *sb, unsigned mode)
10031034
attr.mode = mode;
10041035
attr.ino = FUSE_ROOT_ID;
10051036
attr.nlink = 1;
1006-
return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0);
1037+
return fuse_iget(sb, FUSE_ROOT_ID, 0, &attr, 0, 0, 0);
10071038
}
10081039

10091040
struct fuse_inode_handle {
@@ -1612,7 +1643,8 @@ static int fuse_fill_super_submount(struct super_block *sb,
16121643
return -ENOMEM;
16131644

16141645
fuse_fill_attr_from_inode(&root_attr, parent_fi);
1615-
root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0);
1646+
root = fuse_iget(sb, parent_fi->nodeid, 0, &root_attr, 0, 0,
1647+
fuse_get_evict_ctr(fm->fc));
16161648
/*
16171649
* This inode is just a duplicate, so it is not looked up and
16181650
* its nlookup should not be incremented. fuse_iget() does

fs/fuse/readdir.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ static int parse_dirfile(char *buf, size_t nbytes, struct file *file,
149149

150150
static int fuse_direntplus_link(struct file *file,
151151
struct fuse_direntplus *direntplus,
152-
u64 attr_version)
152+
u64 attr_version, u64 evict_ctr)
153153
{
154154
struct fuse_entry_out *o = &direntplus->entry_out;
155155
struct fuse_dirent *dirent = &direntplus->dirent;
@@ -233,7 +233,7 @@ static int fuse_direntplus_link(struct file *file,
233233
} else {
234234
inode = fuse_iget(dir->i_sb, o->nodeid, o->generation,
235235
&o->attr, ATTR_TIMEOUT(o),
236-
attr_version);
236+
attr_version, evict_ctr);
237237
if (!inode)
238238
inode = ERR_PTR(-ENOMEM);
239239

@@ -284,7 +284,8 @@ static void fuse_force_forget(struct file *file, u64 nodeid)
284284
}
285285

286286
static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file,
287-
struct dir_context *ctx, u64 attr_version)
287+
struct dir_context *ctx, u64 attr_version,
288+
u64 evict_ctr)
288289
{
289290
struct fuse_direntplus *direntplus;
290291
struct fuse_dirent *dirent;
@@ -319,7 +320,7 @@ static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file,
319320
buf += reclen;
320321
nbytes -= reclen;
321322

322-
ret = fuse_direntplus_link(file, direntplus, attr_version);
323+
ret = fuse_direntplus_link(file, direntplus, attr_version, evict_ctr);
323324
if (ret)
324325
fuse_force_forget(file, direntplus->entry_out.nodeid);
325326
}
@@ -337,7 +338,7 @@ static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx)
337338
struct fuse_io_args ia = {};
338339
struct fuse_args_pages *ap = &ia.ap;
339340
struct fuse_folio_desc desc = { .length = PAGE_SIZE };
340-
u64 attr_version = 0;
341+
u64 attr_version = 0, evict_ctr = 0;
341342
bool locked;
342343

343344
folio = folio_alloc(GFP_KERNEL, 0);
@@ -351,6 +352,7 @@ static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx)
351352
ap->descs = &desc;
352353
if (plus) {
353354
attr_version = fuse_get_attr_version(fm->fc);
355+
evict_ctr = fuse_get_evict_ctr(fm->fc);
354356
fuse_read_args_fill(&ia, file, ctx->pos, PAGE_SIZE,
355357
FUSE_READDIRPLUS);
356358
} else {
@@ -368,7 +370,8 @@ static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx)
368370
fuse_readdir_cache_end(file, ctx->pos);
369371
} else if (plus) {
370372
res = parse_dirplusfile(folio_address(folio), res,
371-
file, ctx, attr_version);
373+
file, ctx, attr_version,
374+
evict_ctr);
372375
} else {
373376
res = parse_dirfile(folio_address(folio), res, file,
374377
ctx);

0 commit comments

Comments
 (0)