From 7259fb011c14977661b7a4680886b466a3089ea7 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Sat, 24 Oct 2026 21:20:28 +0200 Subject: [PATCH 1/4] erofs: hold real inodes for shared files EROFS page cache sharing uses shared inodes. However, a mmaped VMA keeps the shared file even after the corresponding fd is closed, while currently the real inode is never held by any reference associated with the shared file. As a result, the real inode could be evicted and removed from the shared inode's list while the page fault can still invoke on the shared inode's address space, which causes no candidate is successfully acquired by erofs_real_inode(). Fix this by simply keeping the original inode in the shared backing file's private data. Also replace confusing "realfile"/"rf" names with "sf" (sharedfile) to unify the terms as well as nullify realinode in erofs_real_inode() to avoid returning an uninitialized pointer. Fixes: 5ef3208e3be5 ("erofs: introduce the page cache share feature") Reported-by: YoungWoo Rim Closes: https://lore.kernel.org/r/atzSVuvqYKgmvDmO@MacBookPro Cc: stable@vger.kernel.org # 7.0+ Reviewed-by: Chao Yu Signed-off-by: Gao Xiang (Docker) --- fs/erofs/ishare.c | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index db32d16517a5..ea146fa8a27c 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -30,9 +30,18 @@ static int erofs_ishare_iget5_set(struct inode *inode, void *data) return 0; } +static int erofs_ishare_backing_release(struct inode *inode, struct file *file) +{ + iput(file->private_data); + file->private_data = NULL; + return 0; +} + bool erofs_ishare_fill_inode(struct inode *inode) { - static const struct file_operations empty_fops = {}; + static const struct file_operations backing_fops = { + .release = erofs_ishare_backing_release, + }; struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb); const struct address_space_operations *aops; struct erofs_inode *vi = EROFS_I(inode); @@ -50,7 +59,7 @@ 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_fop = &backing_fops; si->i_mapping->a_ops = aops; si->i_mode = 0444 | S_IFREG; si->i_size = inode->i_size; @@ -102,16 +111,24 @@ static int erofs_ishare_file_open(struct inode *inode, struct file *file) .mnt = erofs_ishare_mnt, .dentry = EROFS_I(inode)->sharedentry, }; - struct file *rf; + struct file *sf; if (file->f_flags & O_DIRECT) return -EINVAL; - rf = backing_file_open(file, file->f_flags | O_NOATIME, + sf = backing_file_open(file, file->f_flags | O_NOATIME, &sharedpath, current_cred()); - if (IS_ERR(rf)) - return PTR_ERR(rf); - file->private_data = rf; + if (IS_ERR(sf)) + return PTR_ERR(sf); + /* + * A VMA only holds a reference to the shared file so that the original + * file may be released: the shared file must therefore hold a reference + * to one of the real inodes ("inode" passed here), and that reference + * will be released by erofs_ishare_backing_release(). + */ + ihold(inode); + sf->private_data = inode; + file->private_data = sf; return 0; } @@ -125,13 +142,13 @@ static int erofs_ishare_file_release(struct inode *inode, struct file *file) static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb, struct iov_iter *to) { - struct file *realfile = iocb->ki_filp->private_data; + struct file *sf = iocb->ki_filp->private_data; struct kiocb dedup_iocb; ssize_t nread; if (!iov_iter_count(to)) return 0; - kiocb_clone(&dedup_iocb, iocb, realfile); + kiocb_clone(&dedup_iocb, iocb, sf); nread = filemap_read(&dedup_iocb, to, 0); iocb->ki_pos = dedup_iocb.ki_pos; return nread; @@ -139,12 +156,12 @@ static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb, static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma) { - struct file *realfile = file->private_data; + struct file *sf = file->private_data; int err; - vma_set_file(vma, realfile); + vma_set_file(vma, sf); - err = security_mmap_backing_file(vma, realfile, file); + err = security_mmap_backing_file(vma, sf, file); if (err) return err; @@ -178,7 +195,7 @@ const struct file_operations erofs_ishare_fops = { struct inode *erofs_real_inode(struct inode *inode, bool *need_iput) { struct erofs_inode *vi, *vi_share; - struct inode *realinode; + struct inode *realinode = NULL; *need_iput = false; if (inode->i_sb != erofs_ishare_mnt->mnt_sb) From 52dceb88082b4c7983d1c8c7de1c181f018055ac Mon Sep 17 00:00:00 2001 From: Hengyu Liang Date: Fri, 23 Oct 2026 15:00:51 -0400 Subject: [PATCH 2/4] erofs: fix metadata reads at the end of file-backed images Commit e587a984d332 ("erofs: use dedicated meta inodes for file-backed mounts") made erofs_read_meta_folio() read the metadata of a file-backed mount one whole folio at a time. However, an image only has to be a multiple of its block size. With a block size below the page size, it can end in the middle of a page. The read of that last page is then short, and erofs_fileio_ki_complete() fails it with -EIO. As of now, inodes in the last page of such an image cannot be read, and an image smaller than a page cannot be mounted. The issue can be reproduced with util-linux 2.41, whose mount uses the image file directly: mkdir src for i in $(seq 50); do echo $i > src/$i; done mkfs.erofs -b512 test.erofs src mount -t erofs test.erofs /mnt ls -l /mnt Before that commit, ls -l lists all 50 files. After it, ls -l reports "Input/output error" for 24 of them. This patch reads the folio only up to the end of the backing file and zeroes the rest, as the page cache of the backing file did before. A folio that lies wholly past the end is zeroed without a read. Fixes: e587a984d332 ("erofs: use dedicated meta inodes for file-backed mounts") Cc: stable@vger.kernel.org Signed-off-by: Hengyu Liang Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang (Docker) --- fs/erofs/fileio.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/fs/erofs/fileio.c b/fs/erofs/fileio.c index c36a90a1a4cb..e13293ec70de 100644 --- a/fs/erofs/fileio.c +++ b/fs/erofs/fileio.c @@ -142,17 +142,29 @@ int erofs_read_meta_folio(struct file *file, struct folio *folio) .dev = { .m_pa = folio_pos(folio), }, }; struct inode *inode = folio_inode(folio); + size_t len = folio_size(folio); + loff_t pos, isize; int err; err = erofs_map_dev(inode->i_sb, &io.dev); if (err) return err; + pos = io.dev.m_dif->fsoff + io.dev.m_pa; + isize = i_size_read(file_inode(io.dev.m_dif->file)); + if (pos + folio_size(folio) > isize) { + len = isize > pos ? isize - pos : 0; + folio_zero_segment(folio, len, folio_size(folio)); + if (!len) { + folio_end_read(folio, true); + return 0; + } + } + io.rq = erofs_fileio_rq_alloc(inode->i_sb, io.dev.m_dif->file); io.rq->bio.bi_end_io = erofs_fileio_meta_endio; - io.rq->bio.bi_iter.bi_sector = - (io.dev.m_dif->fsoff + io.dev.m_pa) >> 9; - bio_add_folio_nofail(&io.rq->bio, folio, folio_size(folio), 0); + io.rq->bio.bi_iter.bi_sector = pos >> 9; + bio_add_folio_nofail(&io.rq->bio, folio, len, 0); erofs_fileio_rq_submit(io.rq); return 0; } From aafea970fb04633e22c7ad74cba4caf563c6d6a9 Mon Sep 17 00:00:00 2001 From: Zhan Xusheng Date: Mon, 26 Oct 2026 17:22:17 +0800 Subject: [PATCH 3/4] erofs: fix disallowing superblock_share if device= is set superblock_share together with device= is rejected in erofs_fc_fill_super(): if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) && sbi->devs->extra_devices) return -EINVAL; but extra_devices is kept in sbi->devs, not in sbi->opt, and erofs_sb_share_test_super() compares only the backing inode, the fsoffset and sbi->opt. So once the image is mounted with superblock_share, a second mount with superblock_share,device= matches that superblock and get_tree_super() reuses it without calling fill_super() at all. The same command line therefore either fails with -EINVAL or succeeds with device= quietly dropped, depending on whether a compatible mount already exists. Refuse to match a context that brought device= options, so that it gets a superblock of its own and fill_super() decides as it does today. Only the incoming context can be tested: a shared superblock may have extra_devices set itself, because erofs_scan_devices() fills the device table from the image when no device= was given. Fixes: cd4108319bb3 ("erofs: reuse superblock for file-backed mounts") Signed-off-by: Zhan Xusheng Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang (Docker) --- fs/erofs/super.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index 16fa3b853f1b..8ca44b1c0bd1 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -827,7 +827,8 @@ static int erofs_sb_share_test_super(struct super_block *sb, return 0; return file_inode(sbi->dif0.file) == file_inode(new_sbi->dif0.file) && sbi->dif0.fsoff == new_sbi->dif0.fsoff && - !memcmp(&sbi->opt, &new_sbi->opt, sizeof(sbi->opt)); + !memcmp(&sbi->opt, &new_sbi->opt, sizeof(sbi->opt)) && + !new_sbi->devs->extra_devices; } static int erofs_file_get_tree(struct fs_context *fc) From 209108a5456171ab430e25b54b2dfe083455bfb6 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Tue, 27 Oct 2026 09:07:46 +0100 Subject: [PATCH 4/4] erofs: drop unnecessary .read_folio() checks for file-backed mounts Since currently dedicated EROFS-owned meta inodes are used for file-backed mounts, .read_folio() checks are now redundant. As a result, EROFS file-back mounts can now apply to tmpfs too. Fixes: e587a984d332 ("erofs: use dedicated meta inodes for file-backed mounts") Signed-off-by: Gao Xiang (Docker) --- fs/erofs/super.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/erofs/super.c b/fs/erofs/super.c index 8ca44b1c0bd1..ce05557bd998 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -851,8 +851,7 @@ static int erofs_fc_get_tree(struct fs_context *fc) errorfc(fc, "source fd option not supported"); return -EINVAL; } - if (!S_ISREG(file_inode(sbi->dif0.file)->i_mode) || - !sbi->dif0.file->f_mapping->a_ops->read_folio) { + if (!S_ISREG(file_inode(sbi->dif0.file)->i_mode)) { errorfc(fc, "source is unsupported"); return -EINVAL; } @@ -872,8 +871,7 @@ static int erofs_fc_get_tree(struct fs_context *fc) return PTR_ERR(file); sbi->dif0.file = file; - if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) && - sbi->dif0.file->f_mapping->a_ops->read_folio) + if (S_ISREG(file_inode(sbi->dif0.file)->i_mode)) return erofs_file_get_tree(fc); } return ret;