-----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEq1nRK9aeMoq1VSgcnJ2qBz9kQNkFAmrt7bwACgkQnJ2qBz9k
 QNlW2AgA7AZxlZRg+7irymdWH89Oncn0axt9jMH0eL2SArC004f5g+Sgw38824VK
 iNiAlzng/oXvquUfS58HHLLr2TByKJe7S8Jeyz8lohZISRyY/uHfChm1NgDbezvZ
 tYZ4djrM7hc9S1XVbDDaT4yhewvW/KNGsNXdLvpkD4XMjCT1ij9LtELH7H27Ngop
 XGVkAfjlTlSKvg5b+VTRFjgjbjbLLZMo2RafcVXCWhrkYXTtZHmU5h1kPI+2ENYb
 Azew+HkqGINDRsbxsE9BbCEhiFexCOALrIcWMd9OGIeK9xgLe84exCKVknCwkDGO
 nXNf90PbVtg/IxK486cXndCf6usnFA==
 =I9am
 -----END PGP SIGNATURE-----

Merge tag 'fs_for_v7.4-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs

Pull udf, ext2, isofs and quota fixes from Jan Kara:
 - Some disk format validation improvements for udf
 - Fix isofs handling of dates before 1900
 - Fix ext2 inadvertedly syncing buffer on every directory modification
 - Fix ext2 O_DSYNC handling for direct IO
 - A couple of smaller ext2 fixes
 - Fix tmpfs quotas for root filesystem
 - Unify my email addresses in MAINTAINERS file

* tag 'fs_for_v7.4-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/jack/linux-fs:
  udf: Fix incorrect error code in udf_current_aext()
  udf: Simplify udf_table_prealloc_blocks()
  udf: don't use eloc after the end of the unallocated space table
  udf: fix preallocation discarding before an empty allocation extent
  udf: check the unallocated space table when it is loaded
  udf: don't let the extent type hide an exhausted free-space table extent
  ext2: preserve IOMAP_F_DIRTY for extending direct writes
  udf, ext2, quota, jbd2: Unify email addresses in MAINTAINERS
  isofs: handle Rock Ridge long-form dates before 1900
  ext2: only flush DIRSYNC directories on every change
  ext2: preserve completed DIO bytes when buffered fallback fails
  ext2: take invalidate_lock in ext2_write_failed()
  ext2: Make sbi an proper alias for EXT2_SB(sb) in ext2_fill_super()
  udf: Validate extended attribute space is large enough
  quota: initialize dquot cache before mounting rootfs
  udf: validate vat20->lengthHeader and VAT inode size in udf_load_vat()
This commit is contained in:
Linus Torvalds 2026-11-06 09:00:12 -08:00
commit b556f1c4dc
13 changed files with 157 additions and 55 deletions

View File

@ -7838,7 +7838,7 @@ W: http://www.win.tue.nl/~aeb/linux/zip/zip-1.html
W: http://www.win.tue.nl/~aeb/partitions/partition_types-1.html
DISKQUOTA
M: Jan Kara <jack@suse.com>
M: Jan Kara <jack@suse.cz>
S: Maintained
F: Documentation/filesystems/quota.rst
F: fs/quota/
@ -10109,7 +10109,7 @@ F: drivers/leds/leds-expresswire.c
F: include/linux/leds-expresswire.h
EXT2 FILE SYSTEM
M: Jan Kara <jack@suse.com>
M: Jan Kara <jack@suse.cz>
L: linux-ext4@vger.kernel.org
S: Maintained
F: Documentation/filesystems/ext2.rst
@ -14324,7 +14324,7 @@ F: include/uapi/linux/jffs2.h
JOURNALLING LAYER FOR BLOCK DEVICES (JBD2)
M: "Theodore Ts'o" <tytso@mit.edu>
M: Jan Kara <jack@suse.com>
M: Jan Kara <jack@suse.cz>
L: linux-ext4@vger.kernel.org
S: Maintained
F: fs/jbd2/
@ -28714,7 +28714,7 @@ F: arch/m68k/coldfire/
F: arch/m68k/include/asm/*_no.*
UDF FILESYSTEM
M: Jan Kara <jack@suse.com>
M: Jan Kara <jack@suse.cz>
S: Maintained
F: Documentation/filesystems/udf.rst
F: fs/udf/

View File

@ -33,6 +33,7 @@
#include <linux/rculist_bl.h>
#include <linux/list_lru.h>
#include <linux/namei.h>
#include <linux/quotaops.h>
#include "internal.h"
#include "mount.h"
@ -3690,6 +3691,7 @@ void __init vfs_caches_init(void)
inode_init();
files_init();
files_maxfiles_init();
dquot_cache_init();
mnt_init();
bdev_cache_init();
chrdev_init();

View File

@ -442,6 +442,9 @@ static int ext2_handle_dirsync(struct inode *dir)
{
int err;
if (!IS_DIRSYNC(dir))
return 0;
err = filemap_write_and_wait(dir->i_mapping);
if (!err)
err = sync_inode_metadata(dir, 1);

View File

@ -145,7 +145,8 @@ static ssize_t ext2_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
pos = iocb->ki_pos;
status = generic_perform_write(iocb, from);
if (unlikely(status < 0)) {
ret = status;
if (!ret)
ret = status;
goto out_unlock;
}

View File

@ -58,8 +58,10 @@ void ext2_write_failed(struct address_space *mapping, loff_t to)
struct inode *inode = mapping->host;
if (to > inode->i_size) {
filemap_invalidate_lock(mapping);
truncate_pagecache(inode, inode->i_size);
ext2_truncate_blocks(inode, inode->i_size);
filemap_invalidate_unlock(mapping);
}
}
@ -805,6 +807,12 @@ static int ext2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
(first_block << blkbits) < i_size_read(inode))
create = 0;
ret = ext2_get_blocks(inode, first_block, max_blocks,
&bno, &new, &boundary, create);
if (ret < 0)
return ret;
iomap->flags = 0;
/*
* Writes that span EOF might trigger an IO size update on completion,
* so consider them to be dirty for the purposes of O_DSYNC even if
@ -812,13 +820,6 @@ static int ext2_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
*/
if ((flags & IOMAP_WRITE) && offset + length > i_size_read(inode))
iomap->flags |= IOMAP_F_DIRTY;
ret = ext2_get_blocks(inode, first_block, max_blocks,
&bno, &new, &boundary, create);
if (ret < 0)
return ret;
iomap->flags = 0;
iomap->offset = (u64)first_block << blkbits;
iomap->bdev = inode->i_sb->s_bdev;
@ -1236,9 +1237,7 @@ static void ext2_truncate_blocks(struct inode *inode, loff_t offset)
if (ext2_inode_is_fast_symlink(inode))
return;
filemap_invalidate_lock(inode->i_mapping);
__ext2_truncate_blocks(inode, offset);
filemap_invalidate_unlock(inode->i_mapping);
}
static int ext2_setsize(struct inode *inode, loff_t newsize)

View File

@ -883,17 +883,18 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc)
__le32 features;
int err;
sbi = kzalloc_obj(*sbi);
if (!sbi)
sb->s_fs_info = kzalloc_obj(*sbi);
if (!sb->s_fs_info)
return -ENOMEM;
sbi = EXT2_SB(sb);
sbi->s_blockgroup_lock =
kzalloc_obj(struct blockgroup_lock);
if (!sbi->s_blockgroup_lock) {
kfree(sbi);
kfree(sb->s_fs_info);
sb->s_fs_info = NULL;
return -ENOMEM;
}
sb->s_fs_info = sbi;
sbi->s_sb_block = sb_block;
guard(spinlock_init)(&EXT2_SB(sb)->s_lock);

View File

@ -25,7 +25,7 @@ struct timespec64 iso_date(u8 *p, int flags)
year = (p[0] - '0') * 1000 +
(p[1] - '0') * 100 +
(p[2] - '0') * 10 +
(p[3] - '0') - 1900;
(p[3] - '0');
month = ((p[4] - '0') * 10 + (p[5] - '0'));
day = ((p[6] - '0') * 10 + (p[7] - '0'));
hour = ((p[8] - '0') * 10 + (p[9] - '0'));
@ -34,7 +34,7 @@ struct timespec64 iso_date(u8 *p, int flags)
ts.tv_nsec = ((p[14] - '0') * 10 + (p[15] - '0')) * 10000000;
tz = p[16];
} else {
year = p[0];
year = p[0] + 1900;
month = p[1];
day = p[2];
hour = p[3];
@ -45,10 +45,10 @@ struct timespec64 iso_date(u8 *p, int flags)
tz = flags & ISO_DATE_HIGH_SIERRA ? 0 : p[6];
}
if (year < 0) {
if (year < 1) {
ts.tv_sec = 0;
} else {
ts.tv_sec = mktime64(year+1900, month, day, hour, minute, second);
ts.tv_sec = mktime64(year, month, day, hour, minute, second);
/* sign extend */
if (tz & 0x80)

View File

@ -2958,7 +2958,7 @@ static const struct ctl_table fs_dqstats_table[] = {
},
};
static int __init dquot_init(void)
void __init dquot_cache_init(void)
{
int i, ret;
unsigned long nr_hash;
@ -2966,8 +2966,6 @@ static int __init dquot_init(void)
printk(KERN_NOTICE "VFS: Disk quotas %s\n", __DQUOT_VERSION__);
register_sysctl_init("fs/quota", fs_dqstats_table);
dquot_cachep = kmem_cache_create("dquot",
sizeof(struct dquot), sizeof(unsigned long) * 4,
(SLAB_HWCACHE_ALIGN|SLAB_RECLAIM_ACCOUNT|
@ -3008,7 +3006,11 @@ static int __init dquot_init(void)
WQ_UNBOUND | WQ_MEM_RECLAIM, WQ_MAX_ACTIVE);
if (!quota_unbound_wq)
panic("Cannot create quota_unbound_wq\n");
}
static int __init init_fs_dquot_sysctls(void)
{
register_sysctl_init("fs/quota", fs_dqstats_table);
return 0;
}
fs_initcall(dquot_init);
fs_initcall(init_fs_dquot_sysctls);

View File

@ -457,6 +457,7 @@ static void udf_table_free_blocks(struct super_block *sb,
int adsize;
eloc.partitionReferenceNum = bloc->partitionReferenceNum;
eloc.logicalBlockNum = start;
elen = EXT_RECORDED_ALLOCATED |
(count << sb->s_blocksize_bits);
@ -524,30 +525,22 @@ static int udf_table_prealloc_blocks(struct super_block *sb,
while (first_block != eloc.logicalBlockNum) {
ret = udf_next_aext(table, &epos, &eloc, &elen, &etype, 1);
if (ret < 0)
if (ret <= 0)
goto err_out;
if (ret == 0)
break;
udf_debug("eloc=%u, elen=%u, first_block=%u\n",
eloc.logicalBlockNum, elen, first_block);
}
if (first_block == eloc.logicalBlockNum) {
epos.offset -= adsize;
alloc_count = (elen >> sb->s_blocksize_bits);
if (alloc_count > block_count) {
alloc_count = block_count;
eloc.logicalBlockNum += alloc_count;
elen -= (alloc_count << sb->s_blocksize_bits);
udf_write_aext(table, &epos, &eloc,
(etype << 30) | elen, 1);
} else
udf_delete_aext(table, epos, &freed);
} else {
alloc_count = 0;
}
epos.offset -= adsize;
alloc_count = (elen >> sb->s_blocksize_bits);
if (alloc_count > block_count) {
alloc_count = block_count;
eloc.logicalBlockNum += alloc_count;
elen -= (alloc_count << sb->s_blocksize_bits);
udf_write_aext(table, &epos, &eloc, (etype << 30) | elen, 1);
} else
udf_delete_aext(table, epos, &freed);
err_out:
brelse(epos.bh);
@ -572,7 +565,7 @@ static udf_pblk_t udf_table_new_block(struct super_block *sb,
uint32_t elen, goal_elen = 0;
struct kernel_lb_addr eloc, goal_eloc;
struct extent_position epos, goal_epos;
int8_t etype;
int8_t etype, goal_etype = 0;
struct udf_inode_info *iinfo = UDF_I(table);
int ret = 0;
@ -623,7 +616,8 @@ static udf_pblk_t udf_table_new_block(struct super_block *sb,
goal_epos.block = epos.block;
goal_epos.offset = epos.offset - adsize;
goal_eloc = eloc;
goal_elen = (etype << 30) | elen;
goal_elen = elen;
goal_etype = etype;
}
}
@ -647,7 +641,8 @@ static udf_pblk_t udf_table_new_block(struct super_block *sb,
goal_elen -= sb->s_blocksize;
if (goal_elen)
udf_write_aext(table, &goal_epos, &goal_eloc, goal_elen, 1);
udf_write_aext(table, &goal_epos, &goal_eloc,
(goal_etype << 30) | goal_elen, 1);
else
udf_delete_aext(table, goal_epos, &freed);
brelse(goal_epos.bh);

View File

@ -1607,6 +1607,11 @@ static int udf_read_inode(struct inode *inode, bool hidden_inode)
*/
if (iinfo->i_lenEAttr > bs || iinfo->i_lenAlloc > bs)
goto out;
/* We need to have space at least for generic descriptor */
if (iinfo->i_lenEAttr && iinfo->i_lenEAttr <
sizeof(struct extendedAttrHeaderDesc))
goto out;
/* Now do exact checks */
if (udf_file_entry_alloc_offset(inode) + iinfo->i_lenAlloc > bs)
goto out;
@ -2325,10 +2330,10 @@ int udf_current_aext(struct inode *inode, struct extent_position *epos,
ptr = epos->bh->b_data + epos->offset;
if (check_add_overflow(sizeof(struct allocExtDesc),
le32_to_cpu(header->lengthAllocDescs), &alen))
return -1;
return -EFSCORRUPTED;
if (alen > epos->bh->b_size)
return -1;
return -EFSCORRUPTED;
}
switch (iinfo->i_alloc_type) {

View File

@ -1106,6 +1106,73 @@ static int check_partition_desc(struct super_block *sb,
return 0;
}
/*
* Check the Unallocated Space Table once when it is loaded: the allocator
* hands out blocks from the start of each extent and trusts that every
* extent covers at least one whole block inside the partition.
*/
static int udf_check_unalloc_table(struct super_block *sb,
struct inode *table, u32 partition_len)
{
struct extent_position epos = {
.block = UDF_I(table)->i_location,
.offset = sizeof(struct unallocSpaceEntry),
};
struct kernel_lb_addr eloc;
uint32_t elen, blocks;
struct kernel_lb_addr seen_block = {};
uint32_t seen_offset = 0;
u64 steps = 0, period = 1;
int8_t etype;
int ret;
/* The walk below assumes the layout of an Unallocated Space Entry */
if (!UDF_I(table)->i_use) {
udf_err(sb, "unallocated space table is not an unallocated space entry\n");
return -EFSCORRUPTED;
}
while ((ret = udf_next_aext(table, &epos, &eloc, &elen, &etype, 1)) > 0) {
blocks = elen >> sb->s_blocksize_bits;
if (!blocks || (elen & (sb->s_blocksize - 1)) ||
eloc.logicalBlockNum >= partition_len ||
blocks > partition_len - eloc.logicalBlockNum) {
udf_err(sb, "invalid unallocated space table extent (block %u, length %u)\n",
eloc.logicalBlockNum, elen);
ret = -EFSCORRUPTED;
break;
}
/*
* A chain of allocation extents can lead back to itself, and
* then the walk returns the same extents forever. Remember a
* position at doubling intervals; a walk that comes back to it
* is in a loop.
*/
if (epos.block.logicalBlockNum == seen_block.logicalBlockNum &&
epos.block.partitionReferenceNum ==
seen_block.partitionReferenceNum &&
epos.offset == seen_offset) {
udf_err(sb, "unallocated space table loops back on itself\n");
ret = -EFSCORRUPTED;
break;
}
if (++steps == period) {
seen_block = epos.block;
seen_offset = epos.offset;
steps = 0;
period <<= 1;
}
if (fatal_signal_pending(current)) {
udf_err(sb, "interrupted while checking the unallocated space table\n");
ret = -EINTR;
break;
}
cond_resched();
}
brelse(epos.bh);
return ret;
}
static int udf_fill_partdesc_info(struct super_block *sb,
struct partitionDesc *p, int p_index)
{
@ -1166,6 +1233,16 @@ static int udf_fill_partdesc_info(struct super_block *sb,
p_index);
return PTR_ERR(inode);
}
err = udf_check_unalloc_table(sb, inode, map->s_partition_len);
if (err) {
iput(inode);
if (err == -EINTR)
return err;
if (!sb_rdonly(sb))
return -EACCES;
UDF_SET_FLAG(sb, UDF_FLAG_RW_INCOMPAT);
return 0;
}
map->s_uspace.s_table = inode;
map->s_partition_flags |= UDF_PART_FLAG_UNALLOC_TABLE;
udf_debug("unallocSpaceTable (part %d) @ %llu\n",
@ -1252,6 +1329,12 @@ static int udf_load_vat(struct super_block *sb, int p_index, int type1_index)
(sbi->s_vat_inode->i_size - 36) >> 2;
} else if (map->s_partition_type == UDF_VIRTUAL_MAP20) {
vati = UDF_I(sbi->s_vat_inode);
if (sbi->s_vat_inode->i_size <
sizeof(struct virtualAllocationTable20)) {
udf_err(sb, "Too short VAT inode size %lld\n",
sbi->s_vat_inode->i_size);
return -EFSCORRUPTED;
}
if (vati->i_alloc_type != ICBTAG_FLAG_AD_IN_ICB) {
int err = 0;
@ -1269,8 +1352,12 @@ static int udf_load_vat(struct super_block *sb, int p_index, int type1_index)
map->s_type_specific.s_virtual.s_start_offset =
le16_to_cpu(vat20->lengthHeader);
if (map->s_type_specific.s_virtual.s_start_offset
> sbi->s_vat_inode->i_size) {
if (map->s_type_specific.s_virtual.s_start_offset <
sizeof(struct virtualAllocationTable20) ||
map->s_type_specific.s_virtual.s_start_offset >
sb->s_blocksize ||
map->s_type_specific.s_virtual.s_start_offset >
sbi->s_vat_inode->i_size) {
udf_err(sb, "Corrupted VAT header length %u (VAT inode size %lld)\n",
map->s_type_specific.s_virtual.s_start_offset,
sbi->s_vat_inode->i_size);
@ -2233,8 +2320,9 @@ static int udf_fill_super(struct super_block *sb, struct fs_context *fc)
/*
* EACCES is special - we want to propagate to
* upper layers that we cannot handle RW mount.
* EINTR means that a fatal signal is pending.
*/
if (ret == -EACCES)
if (ret == -EACCES || ret == -EINTR)
break;
} else
break;

View File

@ -124,8 +124,8 @@ void udf_discard_prealloc(struct inode *inode)
{
struct extent_position epos = {};
struct extent_position prev_epos = {};
struct kernel_lb_addr eloc;
uint32_t elen;
struct kernel_lb_addr eloc, tmpeloc;
uint32_t elen, tmpelen;
uint64_t lbcount = 0;
int8_t etype = -1;
struct udf_inode_info *iinfo = UDF_I(inode);
@ -141,7 +141,8 @@ void udf_discard_prealloc(struct inode *inode)
/* Find the last extent in the file */
while (1) {
ret = udf_next_aext(inode, &epos, &eloc, &elen, &tmpetype, 0);
ret = udf_next_aext(inode, &epos, &tmpeloc, &tmpelen,
&tmpetype, 0);
if (ret < 0)
goto out;
if (ret == 0)

View File

@ -40,6 +40,7 @@ void __quota_error(struct super_block *sb, const char *func,
/*
* declaration of quota_function calls in kernel.
*/
void dquot_cache_init(void);
int dquot_initialize(struct inode *inode);
bool dquot_initialize_needed(struct inode *inode);
void dquot_drop(struct inode *inode);
@ -206,6 +207,10 @@ static inline int sb_has_quota_active(struct super_block *sb, int type)
return 0;
}
static inline void dquot_cache_init(void)
{
}
static inline int dquot_initialize(struct inode *inode)
{
return 0;