| From 1f3868f06855c97a4954c99b36f3fc9eb8f60326 Mon Sep 17 00:00:00 2001 |
| From: Jan Kara <jack@suse.cz> |
| Date: Thu, 8 Dec 2022 13:03:30 +0100 |
| Subject: udf: Fix extending file within last block |
| |
| From: Jan Kara <jack@suse.cz> |
| |
| commit 1f3868f06855c97a4954c99b36f3fc9eb8f60326 upstream. |
| |
| When extending file within last block it can happen that the extent is |
| already rounded to the blocksize and thus contains the offset we want to |
| grow up to. In such case we would mistakenly expand the last extent and |
| make it one block longer than it should be, exposing unallocated block |
| in a file and causing data corruption. Fix the problem by properly |
| detecting this case and bailing out. |
| |
| CC: stable@vger.kernel.org |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| fs/udf/inode.c | 32 +++++++++++++++++--------------- |
| 1 file changed, 17 insertions(+), 15 deletions(-) |
| |
| --- a/fs/udf/inode.c |
| +++ b/fs/udf/inode.c |
| @@ -593,13 +593,17 @@ out: |
| static void udf_do_extend_final_block(struct inode *inode, |
| struct extent_position *last_pos, |
| struct kernel_long_ad *last_ext, |
| - uint32_t final_block_len) |
| + uint32_t new_elen) |
| { |
| - struct super_block *sb = inode->i_sb; |
| uint32_t added_bytes; |
| |
| - added_bytes = final_block_len - |
| - (last_ext->extLength & (sb->s_blocksize - 1)); |
| + /* |
| + * Extent already large enough? It may be already rounded up to block |
| + * size... |
| + */ |
| + if (new_elen <= (last_ext->extLength & UDF_EXTENT_LENGTH_MASK)) |
| + return; |
| + added_bytes = (last_ext->extLength & UDF_EXTENT_LENGTH_MASK) - new_elen; |
| last_ext->extLength += added_bytes; |
| UDF_I(inode)->i_lenExtents += added_bytes; |
| |
| @@ -616,12 +620,12 @@ static int udf_extend_file(struct inode |
| int8_t etype; |
| struct super_block *sb = inode->i_sb; |
| sector_t first_block = newsize >> sb->s_blocksize_bits, offset; |
| - unsigned long partial_final_block; |
| + loff_t new_elen; |
| int adsize; |
| struct udf_inode_info *iinfo = UDF_I(inode); |
| struct kernel_long_ad extent; |
| int err = 0; |
| - int within_final_block; |
| + bool within_last_ext; |
| |
| if (iinfo->i_alloc_type == ICBTAG_FLAG_AD_SHORT) |
| adsize = sizeof(struct short_ad); |
| @@ -637,9 +641,9 @@ static int udf_extend_file(struct inode |
| udf_discard_prealloc(inode); |
| |
| etype = inode_bmap(inode, first_block, &epos, &eloc, &elen, &offset); |
| - within_final_block = (etype != -1); |
| + within_last_ext = (etype != -1); |
| /* We don't expect extents past EOF... */ |
| - WARN_ON_ONCE(etype != -1 && |
| + WARN_ON_ONCE(within_last_ext && |
| elen > ((loff_t)offset + 1) << inode->i_blkbits); |
| |
| if ((!epos.bh && epos.offset == udf_file_entry_alloc_offset(inode)) || |
| @@ -656,19 +660,17 @@ static int udf_extend_file(struct inode |
| extent.extLength |= etype << 30; |
| } |
| |
| - partial_final_block = newsize & (sb->s_blocksize - 1); |
| + new_elen = ((loff_t)offset << inode->i_blkbits) | |
| + (newsize & (sb->s_blocksize - 1)); |
| |
| /* File has extent covering the new size (could happen when extending |
| * inside a block)? |
| */ |
| - if (within_final_block) { |
| + if (within_last_ext) { |
| /* Extending file within the last file block */ |
| - udf_do_extend_final_block(inode, &epos, &extent, |
| - partial_final_block); |
| + udf_do_extend_final_block(inode, &epos, &extent, new_elen); |
| } else { |
| - loff_t add = ((loff_t)offset << sb->s_blocksize_bits) | |
| - partial_final_block; |
| - err = udf_do_extend_file(inode, &epos, &extent, add); |
| + err = udf_do_extend_file(inode, &epos, &extent, new_elen); |
| } |
| |
| if (err < 0) |