erofs: ensure valid f_path for page cache sharing

Previously, backing files for page cache sharing were set up with
f_path left as NULL (only f_inode was valid).  It worked, but a recent
mincore fix relies on f_path.mnt and crashes (found by "erofs/028" on
7.2-rc4):

 BUG: kernel NULL pointer dereference, address: 0000000000000018
 #PF: supervisor read access in kernel mode
 #PF: error_code(0x0000) - not-present page
 PGD 0 P4D 0
 Oops: Oops: 0000 [#1] SMP PTI
 CPU: 3 UID: 0 PID: 675528 Comm: fincore Not tainted 7.2.0-rc4-00002-g[]-dirty #1 PREEMPT(lazy)
 Hardware name: Red Hat KVM, BIOS 1.16.0-4.al8 04/01/2014
 RIP: 0010:__do_sys_mincore+0xc0/0x2c0
 ...

Specify valid paths using valid disconnected dentries together with
erofs_ishare_mnt instead of leaving f_path empty, so they are more
like real backing files in a pseudo filesystem and standard
backing_file_open() can be used directly.

Fixes: e187bc02f8 ("mm: do file ownership checks with the proper mount idmap")
Acked-by: Hongbo Li <hongbohbli@tencent.com>
Signed-off-by: Gao Xiang <xiang@kernel.org>
This commit is contained in:
Gao Xiang 2026-07-27 12:27:39 +08:00
parent 97ab5aa517
commit 96b2dbbe58
3 changed files with 30 additions and 31 deletions

View File

@ -189,6 +189,7 @@ config EROFS_FS_PCPU_KTHREAD_HIPRI
config EROFS_FS_PAGE_CACHE_SHARE
bool "EROFS page cache share support (experimental)"
depends on EROFS_FS && EROFS_FS_XATTR
select FS_STACK
help
This enables page cache sharing among inodes with identical
content fingerprints on the same machine.

View File

@ -288,8 +288,8 @@ struct erofs_inode {
struct erofs_inode_fingerprint fingerprint;
spinlock_t ishare_lock;
};
/* for each real inode */
struct inode *sharedinode;
/* for each real filesystem inode */
struct dentry *sharedentry;
};
#endif
/* the corresponding vfs inode */

View File

@ -2,14 +2,13 @@
/*
* Copyright (C) 2024, Alibaba Cloud
*/
#include <linux/backing-file.h>
#include <linux/xxhash.h>
#include <linux/mount.h>
#include <linux/security.h>
#include "internal.h"
#include "xattr.h"
#include "../internal.h"
static struct vfsmount *erofs_ishare_mnt;
static int erofs_ishare_iget5_eq(struct inode *inode, void *data)
@ -33,10 +32,12 @@ static int erofs_ishare_iget5_set(struct inode *inode, void *data)
bool erofs_ishare_fill_inode(struct inode *inode)
{
static const struct file_operations empty_fops = {};
struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb);
const struct address_space_operations *aops;
struct erofs_inode *vi = EROFS_I(inode);
struct erofs_inode_fingerprint fp;
struct dentry *sd;
struct inode *si;
aops = erofs_get_aops(inode);
@ -49,7 +50,9 @@ bool erofs_ishare_fill_inode(struct inode *inode)
xxh32(fp.opaque, fp.size, 0),
erofs_ishare_iget5_eq, erofs_ishare_iget5_set, &fp);
if (si && (inode_state_read_once(si) & I_NEW)) {
si->i_fop = &empty_fops;
si->i_mapping->a_ops = aops;
si->i_mode = 0444 | S_IFREG;
si->i_size = inode->i_size;
unlock_new_inode(si);
} else {
@ -65,7 +68,10 @@ bool erofs_ishare_fill_inode(struct inode *inode)
return false;
}
}
vi->sharedinode = si;
sd = d_obtain_alias(si); /* disconnected denties for sharedinodes */
if (IS_ERR(sd))
return false;
vi->sharedentry = sd;
INIT_LIST_HEAD(&vi->ishare_list);
spin_lock(&EROFS_I(si)->ishare_lock);
list_add(&vi->ishare_list, &EROFS_I(si)->ishare_list);
@ -75,48 +81,40 @@ bool erofs_ishare_fill_inode(struct inode *inode)
void erofs_ishare_free_inode(struct inode *inode)
{
struct erofs_inode *vi = EROFS_I(inode);
struct inode *sharedinode = vi->sharedinode;
struct erofs_inode *vi = EROFS_I(inode), *svi;
if (!sharedinode)
if (!vi->sharedentry)
return;
spin_lock(&EROFS_I(sharedinode)->ishare_lock);
svi = EROFS_I(d_inode(vi->sharedentry));
spin_lock(&svi->ishare_lock);
list_del(&vi->ishare_list);
spin_unlock(&EROFS_I(sharedinode)->ishare_lock);
iput(sharedinode);
vi->sharedinode = NULL;
spin_unlock(&svi->ishare_lock);
dput(vi->sharedentry);
vi->sharedentry = NULL;
}
static int erofs_ishare_file_open(struct inode *inode, struct file *file)
{
struct inode *sharedinode = EROFS_I(inode)->sharedinode;
struct file *realfile;
struct path sharedpath = {
.mnt = erofs_ishare_mnt,
.dentry = EROFS_I(inode)->sharedentry,
};
struct file *rf;
if (file->f_flags & O_DIRECT)
return -EINVAL;
realfile = alloc_empty_backing_file(O_RDONLY|O_NOATIME, current_cred(),
file);
if (IS_ERR(realfile))
return PTR_ERR(realfile);
ihold(sharedinode);
realfile->f_op = &erofs_file_fops;
realfile->f_inode = sharedinode;
realfile->f_mapping = sharedinode->i_mapping;
path_get(&file->f_path);
backing_file_set_user_path(realfile, &file->f_path);
file_ra_state_init(&realfile->f_ra, file->f_mapping);
realfile->private_data = EROFS_I(inode);
file->private_data = realfile;
rf = backing_file_open(file, file->f_flags | O_NOATIME,
&sharedpath, current_cred());
if (IS_ERR(rf))
return PTR_ERR(rf);
file->private_data = rf;
return 0;
}
static int erofs_ishare_file_release(struct inode *inode, struct file *file)
{
struct file *realfile = file->private_data;
iput(realfile->f_inode);
fput(realfile);
fput(file->private_data);
file->private_data = NULL;
return 0;
}