diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-04-13 16:59:19 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-04-13 16:59:19 -0700 |
| commit | 230fb3a33efd52613910a3970495b20295557731 (patch) | |
| tree | fd440a82436916a9ac82ba4d59810c3be30af2d1 | |
| parent | a62fe21079978e5134ad863f8a9835eb24c06d43 (diff) | |
| parent | a5242d37c83abe86df95c6941e2ace9f9055ffcb (diff) | |
| download | linux-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.c | 14 | ||||
| -rw-r--r-- | fs/erofs/erofs_fs.h | 4 | ||||
| -rw-r--r-- | fs/erofs/inode.c | 2 | ||||
| -rw-r--r-- | fs/erofs/internal.h | 23 | ||||
| -rw-r--r-- | fs/erofs/super.c | 8 | ||||
| -rw-r--r-- | fs/erofs/xattr.c | 8 | ||||
| -rw-r--r-- | fs/erofs/zdata.c | 22 | ||||
| -rw-r--r-- | fs/erofs/zmap.c | 43 | ||||
| -rw-r--r-- | include/trace/events/erofs.h | 7 |
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, |
