summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-04-13 16:59:19 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-04-13 16:59:19 -0700
commit230fb3a33efd52613910a3970495b20295557731 (patch)
treefd440a82436916a9ac82ba4d59810c3be30af2d1
parenta62fe21079978e5134ad863f8a9835eb24c06d43 (diff)
parenta5242d37c83abe86df95c6941e2ace9f9055ffcb (diff)
downloadlinux-next-230fb3a33efd52613910a3970495b20295557731.tar.gz
linux-next-230fb3a33efd52613910a3970495b20295557731.zip
Merge tag 'erofs-for-7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs
Pull erofs updates from Gao Xiang: - Validate xattr h_shared_count to report -EFSCORRUPTED explicitly for crafted images - Verify metadata accesses for file-backed mounts via rw_verify_area() - Fix FS_IOC_GETFSLABEL to include the trailing NUL byte, consistent with ext4 and xfs - Properly handle 48-bit on-disk blocks/uniaddr for extra devices - Fix an index underflow in the LZ4 in-place decompression that can cause out-of-bounds accesses with crafted images - Minor fixes and cleanups * tag 'erofs-for-7.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs: erofs: error out obviously illegal extents in advance erofs: clean up encoded map flags erofs: fix unsigned underflow in z_erofs_lz4_handle_overlap() erofs: handle 48-bit blocks/uniaddr for extra devices erofs: include the trailing NUL in FS_IOC_GETFSLABEL erofs: ensure all folios are managed in erofs_try_to_free_all_cached_folios() erofs: verify metadata accesses for file-backed mounts erofs: harden h_shared_count in erofs_init_inode_xattrs()
-rw-r--r--fs/erofs/data.c14
-rw-r--r--fs/erofs/erofs_fs.h4
-rw-r--r--fs/erofs/inode.c2
-rw-r--r--fs/erofs/internal.h23
-rw-r--r--fs/erofs/super.c8
-rw-r--r--fs/erofs/xattr.c8
-rw-r--r--fs/erofs/zdata.c22
-rw-r--r--fs/erofs/zmap.c43
-rw-r--r--include/trace/events/erofs.h7
9 files changed, 80 insertions, 51 deletions
diff --git a/fs/erofs/data.c b/fs/erofs/data.c
index f79ee80627d9..132a27deb2f3 100644
--- a/fs/erofs/data.c
+++ b/fs/erofs/data.c
@@ -30,6 +30,20 @@ void *erofs_bread(struct erofs_buf *buf, erofs_off_t offset, bool need_kmap)
{
pgoff_t index = (buf->off + offset) >> PAGE_SHIFT;
struct folio *folio = NULL;
+ loff_t fpos;
+ int err;
+
+ /*
+ * Metadata access for file-backed mounts reuses page cache of backing
+ * fs inodes (only folio data will be needed) to prevent double caching.
+ * However, the data access range must be verified here in advance.
+ */
+ if (buf->file) {
+ fpos = index << PAGE_SHIFT;
+ err = rw_verify_area(READ, buf->file, &fpos, PAGE_SIZE);
+ if (err < 0)
+ return ERR_PTR(err);
+ }
if (buf->page) {
folio = page_folio(buf->page);
diff --git a/fs/erofs/erofs_fs.h b/fs/erofs/erofs_fs.h
index b80c6bb33a58..7871b16c1d33 100644
--- a/fs/erofs/erofs_fs.h
+++ b/fs/erofs/erofs_fs.h
@@ -44,9 +44,9 @@ struct erofs_deviceslot {
u8 tag[64]; /* digest(sha256), etc. */
__le32 blocks_lo; /* total blocks count of this device */
__le32 uniaddr_lo; /* unified starting block of this device */
- __le32 blocks_hi; /* total blocks count MSB */
+ __le16 blocks_hi; /* total blocks count MSB */
__le16 uniaddr_hi; /* unified starting block MSB */
- u8 reserved[50];
+ u8 reserved[52];
};
#define EROFS_DEVT_SLOT_SIZE sizeof(struct erofs_deviceslot)
diff --git a/fs/erofs/inode.c b/fs/erofs/inode.c
index 4b3d21402e10..a188c570087a 100644
--- a/fs/erofs/inode.c
+++ b/fs/erofs/inode.c
@@ -351,7 +351,7 @@ static int erofs_ioctl_get_volume_label(struct inode *inode, void __user *arg)
ret = clear_user(arg, 1);
else
ret = copy_to_user(arg, sbi->volume_name,
- strlen(sbi->volume_name));
+ strlen(sbi->volume_name) + 1);
return ret ? -EFAULT : 0;
}
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index a4f0a42cf8c3..4792490161ec 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -360,20 +360,19 @@ static inline struct folio *erofs_grab_folio_nowait(struct address_space *as,
readahead_gfp_mask(as) & ~__GFP_RECLAIM);
}
-/* Has a disk mapping */
-#define EROFS_MAP_MAPPED 0x0001
+/* Allocated on disk at @m_pa (e.g. NOT a fragment extent) */
+#define EROFS_MAP_MAPPED 0x0001
/* Located in metadata (could be copied from bd_inode) */
-#define EROFS_MAP_META 0x0002
-/* The extent is encoded */
-#define EROFS_MAP_ENCODED 0x0004
-/* The length of extent is full */
-#define EROFS_MAP_FULL_MAPPED 0x0008
+#define EROFS_MAP_META 0x0002
+/* @m_llen may be truncated by the runtime compared to the on-disk record */
+#define EROFS_MAP_PARTIAL_MAPPED 0x0004
+/* The on-disk @m_llen may cover only part of the encoded data */
+#define EROFS_MAP_PARTIAL_REF 0x0008
/* Located in the special packed inode */
-#define __EROFS_MAP_FRAGMENT 0x0010
-/* The extent refers to partial decompressed data */
-#define EROFS_MAP_PARTIAL_REF 0x0020
-
-#define EROFS_MAP_FRAGMENT (EROFS_MAP_MAPPED | __EROFS_MAP_FRAGMENT)
+#define EROFS_MAP_FRAGMENT 0x0010
+/* The encoded on-disk data will be fully handled (decompressed) */
+#define EROFS_MAP_FULL(f) (!((f) & (EROFS_MAP_PARTIAL_MAPPED | \
+ EROFS_MAP_PARTIAL_REF)))
struct erofs_map_blocks {
struct erofs_buf buf;
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 972a0c82198d..802add6652fd 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -129,6 +129,7 @@ static int erofs_init_device(struct erofs_buf *buf, struct super_block *sb,
struct erofs_fscache *fscache;
struct erofs_deviceslot *dis;
struct file *file;
+ bool _48bit;
dis = erofs_read_metabuf(buf, sb, *pos, false);
if (IS_ERR(dis))
@@ -175,8 +176,11 @@ static int erofs_init_device(struct erofs_buf *buf, struct super_block *sb,
dif->file = file;
}
- dif->blocks = le32_to_cpu(dis->blocks_lo);
- dif->uniaddr = le32_to_cpu(dis->uniaddr_lo);
+ _48bit = erofs_sb_has_48bit(sbi);
+ dif->blocks = le32_to_cpu(dis->blocks_lo) |
+ (_48bit ? (u64)le16_to_cpu(dis->blocks_hi) << 32 : 0);
+ dif->uniaddr = le32_to_cpu(dis->uniaddr_lo) |
+ (_48bit ? (u64)le16_to_cpu(dis->uniaddr_hi) << 32 : 0);
sbi->total_blocks += dif->blocks;
*pos += EROFS_DEVT_SLOT_SIZE;
return 0;
diff --git a/fs/erofs/xattr.c b/fs/erofs/xattr.c
index c411df5d9dfc..41e311019a25 100644
--- a/fs/erofs/xattr.c
+++ b/fs/erofs/xattr.c
@@ -85,6 +85,14 @@ static int erofs_init_inode_xattrs(struct inode *inode)
}
vi->xattr_name_filter = le32_to_cpu(ih->h_name_filter);
vi->xattr_shared_count = ih->h_shared_count;
+ if ((u32)vi->xattr_shared_count * sizeof(__le32) >
+ vi->xattr_isize - sizeof(struct erofs_xattr_ibody_header)) {
+ erofs_err(sb, "invalid h_shared_count %u @ nid %llu",
+ vi->xattr_shared_count, vi->nid);
+ erofs_put_metabuf(&buf);
+ ret = -EFSCORRUPTED;
+ goto out_unlock;
+ }
vi->xattr_shared_xattrs = kmalloc_objs(uint, vi->xattr_shared_count);
if (!vi->xattr_shared_xattrs) {
erofs_put_metabuf(&buf);
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index fe8121df9ef2..8a0b15511931 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -520,7 +520,7 @@ static bool z_erofs_should_alloc_cache(struct z_erofs_frontend *fe)
if (cachestrategy <= EROFS_ZIP_CACHE_DISABLED)
return false;
- if (!(fe->map.m_flags & EROFS_MAP_FULL_MAPPED))
+ if (fe->map.m_flags & EROFS_MAP_PARTIAL_MAPPED)
return true;
if (cachestrategy >= EROFS_ZIP_CACHE_READAROUND &&
@@ -605,8 +605,7 @@ static int erofs_try_to_free_all_cached_folios(struct erofs_sb_info *sbi,
if (!folio_trylock(folio))
return -EBUSY;
- if (!erofs_folio_is_managed(sbi, folio))
- continue;
+ DBG_BUGON(!erofs_folio_is_managed(sbi, folio));
pcl->compressed_bvecs[i].page = NULL;
folio_detach_private(folio);
folio_unlock(folio);
@@ -1034,10 +1033,7 @@ static int z_erofs_scan_folio(struct z_erofs_frontend *f,
/* bump split parts first to avoid several separate cases */
++split;
- if (!(map->m_flags & EROFS_MAP_MAPPED)) {
- folio_zero_segment(folio, cur, end);
- tight = false;
- } else if (map->m_flags & __EROFS_MAP_FRAGMENT) {
+ if (map->m_flags & EROFS_MAP_FRAGMENT) {
erofs_off_t fpos = offset + cur - map->m_la;
err = z_erofs_read_fragment(inode->i_sb, folio, cur,
@@ -1046,6 +1042,9 @@ static int z_erofs_scan_folio(struct z_erofs_frontend *f,
if (err)
break;
tight = false;
+ } else if (!(map->m_flags & EROFS_MAP_MAPPED)) {
+ folio_zero_segment(folio, cur, end);
+ tight = false;
} else {
if (!f->pcl) {
err = z_erofs_pcluster_begin(f);
@@ -1081,14 +1080,13 @@ static int z_erofs_scan_folio(struct z_erofs_frontend *f,
f->pcl->length = offset + end - map->m_la;
f->pcl->pageofs_out = map->m_la & ~PAGE_MASK;
}
- if ((map->m_flags & EROFS_MAP_FULL_MAPPED) &&
- !(map->m_flags & EROFS_MAP_PARTIAL_REF) &&
+ if (EROFS_MAP_FULL(map->m_flags) &&
f->pcl->length == map->m_llen)
f->pcl->partial = false;
}
/* shorten the remaining extent to update progress */
map->m_llen = offset + cur - map->m_la;
- map->m_flags &= ~EROFS_MAP_FULL_MAPPED;
+ map->m_flags |= EROFS_MAP_PARTIAL_MAPPED;
if (cur <= pgs) {
split = cur < pgs;
tight = (bs == PAGE_SIZE);
@@ -1842,7 +1840,7 @@ static void z_erofs_pcluster_readmore(struct z_erofs_frontend *f,
map->m_la = end;
err = z_erofs_map_blocks_iter(inode, map,
EROFS_GET_BLOCKS_READMORE);
- if (err || !(map->m_flags & EROFS_MAP_ENCODED))
+ if (err || !(map->m_flags & EROFS_MAP_MAPPED))
return;
/* expand ra for the trailing edge if readahead */
@@ -1854,7 +1852,7 @@ static void z_erofs_pcluster_readmore(struct z_erofs_frontend *f,
end = round_up(end, PAGE_SIZE);
} else {
end = round_up(map->m_la, PAGE_SIZE);
- if (!(map->m_flags & EROFS_MAP_ENCODED) || !map->m_llen)
+ if (!(map->m_flags & EROFS_MAP_MAPPED) || !map->m_llen)
return;
}
diff --git a/fs/erofs/zmap.c b/fs/erofs/zmap.c
index 30775502b56d..72b96e295716 100644
--- a/fs/erofs/zmap.c
+++ b/fs/erofs/zmap.c
@@ -419,7 +419,7 @@ static int z_erofs_map_blocks_fo(struct inode *inode,
if ((flags & EROFS_GET_BLOCKS_FINDTAIL) && ztailpacking)
vi->z_fragmentoff = m.nextpackoff;
- map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_ENCODED;
+ map->m_flags = EROFS_MAP_MAPPED | EROFS_MAP_PARTIAL_MAPPED;
end = (m.lcn + 1ULL) << lclusterbits;
if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD && endoff >= m.clusterofs) {
@@ -435,7 +435,7 @@ static int z_erofs_map_blocks_fo(struct inode *inode,
} else {
if (m.type != Z_EROFS_LCLUSTER_TYPE_NONHEAD) {
end = (m.lcn << lclusterbits) | m.clusterofs;
- map->m_flags |= EROFS_MAP_FULL_MAPPED;
+ map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED;
m.delta[0] = 1;
}
/* get the corresponding first chunk */
@@ -473,11 +473,6 @@ static int z_erofs_map_blocks_fo(struct inode *inode,
}
if (m.headtype == Z_EROFS_LCLUSTER_TYPE_PLAIN) {
- if (map->m_llen > map->m_plen) {
- DBG_BUGON(1);
- err = -EFSCORRUPTED;
- goto unmap_out;
- }
if (vi->z_advise & Z_EROFS_ADVISE_INTERLACED_PCLUSTER)
map->m_algorithmformat = Z_EROFS_COMPRESSION_INTERLACED;
else
@@ -496,7 +491,7 @@ static int z_erofs_map_blocks_fo(struct inode *inode,
map->m_llen >= i_blocksize(inode))) {
err = z_erofs_get_extent_decompressedlen(&m);
if (!err)
- map->m_flags |= EROFS_MAP_FULL_MAPPED;
+ map->m_flags &= ~EROFS_MAP_PARTIAL_MAPPED;
}
unmap_out:
@@ -594,8 +589,7 @@ static int z_erofs_map_blocks_ext(struct inode *inode,
if (recsz > offsetof(struct z_erofs_extent, pstart_lo))
vi->z_fragmentoff |= map->m_pa << 32;
} else if (map->m_plen & Z_EROFS_EXTENT_PLEN_MASK) {
- map->m_flags |= EROFS_MAP_MAPPED |
- EROFS_MAP_FULL_MAPPED | EROFS_MAP_ENCODED;
+ map->m_flags |= EROFS_MAP_MAPPED;
fmt = map->m_plen >> Z_EROFS_EXTENT_PLEN_FMT_BIT;
if (map->m_plen & Z_EROFS_EXTENT_PLEN_PARTIAL)
map->m_flags |= EROFS_MAP_PARTIAL_REF;
@@ -714,17 +708,28 @@ static int z_erofs_map_sanity_check(struct inode *inode,
struct erofs_sb_info *sbi = EROFS_I_SB(inode);
u64 pend;
- if (!(map->m_flags & EROFS_MAP_ENCODED))
+ if (!(map->m_flags & EROFS_MAP_MAPPED))
return 0;
if (unlikely(map->m_algorithmformat >= Z_EROFS_COMPRESSION_RUNTIME_MAX)) {
erofs_err(inode->i_sb, "unknown algorithm %d @ pos %llu for nid %llu, please upgrade kernel",
map->m_algorithmformat, map->m_la, EROFS_I(inode)->nid);
return -EOPNOTSUPP;
}
- if (unlikely(map->m_algorithmformat < Z_EROFS_COMPRESSION_MAX &&
- !(sbi->available_compr_algs & (1 << map->m_algorithmformat)))) {
- erofs_err(inode->i_sb, "inconsistent algorithmtype %u for nid %llu",
- map->m_algorithmformat, EROFS_I(inode)->nid);
+
+ if (map->m_algorithmformat < Z_EROFS_COMPRESSION_MAX) {
+ if (sbi->available_compr_algs ^ BIT(map->m_algorithmformat)) {
+ erofs_err(inode->i_sb, "inconsistent algorithmtype %u for nid %llu",
+ map->m_algorithmformat, EROFS_I(inode)->nid);
+ return -EFSCORRUPTED;
+ }
+ if (EROFS_MAP_FULL(map->m_flags) && map->m_llen < map->m_plen) {
+ erofs_err(inode->i_sb, "too much compressed data @ la %llu of nid %llu",
+ map->m_la, EROFS_I(inode)->nid);
+ return -EFSCORRUPTED;
+ }
+ } else if (map->m_llen > map->m_plen) {
+ erofs_err(inode->i_sb, "not enough plain data on disk @ la %llu of nid %llu",
+ map->m_la, EROFS_I(inode)->nid);
return -EFSCORRUPTED;
}
if (unlikely(map->m_plen > Z_EROFS_PCLUSTER_MAX_SIZE ||
@@ -781,10 +786,12 @@ static int z_erofs_iomap_begin_report(struct inode *inode, loff_t offset,
iomap->bdev = inode->i_sb->s_bdev;
iomap->offset = map.m_la;
iomap->length = map.m_llen;
- if (map.m_flags & EROFS_MAP_MAPPED) {
+ if (map.m_flags & EROFS_MAP_FRAGMENT) {
+ iomap->type = IOMAP_MAPPED;
+ iomap->addr = IOMAP_NULL_ADDR;
+ } else if (map.m_flags & EROFS_MAP_MAPPED) {
iomap->type = IOMAP_MAPPED;
- iomap->addr = map.m_flags & __EROFS_MAP_FRAGMENT ?
- IOMAP_NULL_ADDR : map.m_pa;
+ iomap->addr = map.m_pa;
} else {
iomap->type = IOMAP_HOLE;
iomap->addr = IOMAP_NULL_ADDR;
diff --git a/include/trace/events/erofs.h b/include/trace/events/erofs.h
index def20d06507b..cd0e3fd8c23f 100644
--- a/include/trace/events/erofs.h
+++ b/include/trace/events/erofs.h
@@ -26,10 +26,9 @@ struct erofs_map_blocks;
#define show_mflags(flags) __print_flags(flags, "", \
{ EROFS_MAP_MAPPED, "M" }, \
{ EROFS_MAP_META, "I" }, \
- { EROFS_MAP_ENCODED, "E" }, \
- { EROFS_MAP_FULL_MAPPED, "F" }, \
- { EROFS_MAP_FRAGMENT, "R" }, \
- { EROFS_MAP_PARTIAL_REF, "P" })
+ { EROFS_MAP_PARTIAL_MAPPED, "T" }, \
+ { EROFS_MAP_PARTIAL_REF, "P" }, \
+ { EROFS_MAP_FRAGMENT, "R" })
TRACE_EVENT(erofs_lookup,