From 97ab5aa5177cfa8759bd3861d71e047ea084a829 Mon Sep 17 00:00:00 2001 From: Hongbo Li Date: Tue, 21 Jul 2026 15:05:18 +0800 Subject: [PATCH 1/3] MAINTAINERS: update Hongbo Li's email address Since the original email account is no longer in use, I have updated it to my current email address. Signed-off-by: Hongbo Li Acked-by: Chao Yu Acked-by: Gao Xiang Signed-off-by: Gao Xiang --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index a674e36529f7..4ac000af898f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9617,7 +9617,7 @@ M: Chao Yu R: Yue Hu R: Jeffle Xu R: Sandeep Dhavale -R: Hongbo Li +R: Hongbo Li R: Chunhai Guo L: linux-erofs@lists.ozlabs.org S: Maintained From 96b2dbbe58a1ea5df8d29c2fe24b5f04715f4443 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Mon, 27 Jul 2026 12:27:39 +0800 Subject: [PATCH 2/3] 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: e187bc02f8fa ("mm: do file ownership checks with the proper mount idmap") Acked-by: Hongbo Li Signed-off-by: Gao Xiang --- fs/erofs/Kconfig | 1 + fs/erofs/internal.h | 4 ++-- fs/erofs/ishare.c | 56 ++++++++++++++++++++++----------------------- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig index 4789b1077d8c..1feb28cfe557 100644 --- a/fs/erofs/Kconfig +++ b/fs/erofs/Kconfig @@ -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. diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 580f8d9f14e7..57bd21859c65 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -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 */ diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c index a1a20a5ba548..fa7d4112dec5 100644 --- a/fs/erofs/ishare.c +++ b/fs/erofs/ishare.c @@ -2,14 +2,13 @@ /* * Copyright (C) 2024, Alibaba Cloud */ +#include #include #include #include #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; } From c9b47e6b23114e939b17f818471c7a46e59006e7 Mon Sep 17 00:00:00 2001 From: Michael Bommarito Date: Tue, 14 Jul 2026 07:47:29 -0400 Subject: [PATCH 3/3] erofs: cap LZMA stream pool size fs/erofs/decompressor_lzma.c sizes the module-global MicroLZMA stream pool from num_possible_cpus() when the lzma_streams module parameter is unset, then z_erofs_load_lzma_config() preallocates one image-supplied dictionary per stream, accepting dictionaries up to 8 MiB. On high-CPU systems, a small EROFS image can pin hundreds of MiB of vmalloc-backed decoder state until the erofs module is unloaded. Impact: An EROFS image mounted by the system can pin up to 8 MiB of vmalloc memory per LZMA stream, either as intended or unexpectedly. Bound the default stream count by a new CONFIG_EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS option, default 16, so the worst-case default preallocation is 128 MiB if the number of CPUs is no less than 16 while preserving the existing per-image dictionary limit. An explicit lzma_streams module parameter is still honoured as-is, so administrators who deliberately size the pool are not affected. Fixes: 622ceaddb764 ("erofs: lzma compression support") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Michael Bommarito Reviewed-by: Gao Xiang Signed-off-by: Gao Xiang --- fs/erofs/Kconfig | 14 ++++++++++++++ fs/erofs/decompressor_lzma.c | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig index 1feb28cfe557..8ca1767dafb6 100644 --- a/fs/erofs/Kconfig +++ b/fs/erofs/Kconfig @@ -131,6 +131,20 @@ config EROFS_FS_ZIP_LZMA Say N if you want to disable LZMA compression support. +config EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS + int "EROFS LZMA default maximum decompression streams" + depends on EROFS_FS_ZIP_LZMA + range 1 NR_CPUS + default 16 + help + By default EROFS allocates one LZMA decompression stream per CPU. + Each stream can hold a dictionary of up to 8 MiB taken from the + mounted image, so on systems with many CPUs this can reserve a lot + of memory. This caps the default; the lzma_streams module parameter + still overrides it. + + If unsure, keep the default of 16. + config EROFS_FS_ZIP_DEFLATE bool "EROFS DEFLATE compressed data support" depends on EROFS_FS_ZIP diff --git a/fs/erofs/decompressor_lzma.c b/fs/erofs/decompressor_lzma.c index f6692d0f2f04..6b0cdb446c6a 100644 --- a/fs/erofs/decompressor_lzma.c +++ b/fs/erofs/decompressor_lzma.c @@ -51,7 +51,8 @@ static int __init z_erofs_lzma_init(void) /* by default, use # of possible CPUs instead */ if (!z_erofs_lzma_nstrms) - z_erofs_lzma_nstrms = num_possible_cpus(); + z_erofs_lzma_nstrms = min_t(unsigned int, num_possible_cpus(), + CONFIG_EROFS_FS_ZIP_LZMA_DEFAULT_MAX_STREAMS); for (i = 0; i < z_erofs_lzma_nstrms; ++i) { struct z_erofs_lzma *strm = kzalloc_obj(*strm);