[PATCH] ext4: only test for inode xattr state when expanding inode

Thadeu Lima de Souza Cascardo posted 1 patch 1 year ago
There is a newer version of this series
fs/ext4/inode.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
[PATCH] ext4: only test for inode xattr state when expanding inode
Posted by Thadeu Lima de Souza Cascardo 1 year ago
When expanding inode size, a check for the xattr magic code could fail
because the underlying data was corrupt or changed by directly writing to
the block device.

But instead of detecting such corruption, the current test would clear the
data but keep the EXT4_STATE_XATTR bit set in the inode state.

When later deleting the inode, this would lead for a test for such bit to
succeed and then an out-of-bounds access.

Since the state could only be set when the magic code has been detected
(and when such bit is cleared, so is the magic code), it is sufficient to
test for such state when deciding whether expanding the inode size is safe.

Here is the KASAN report.

[   35.283769] ==================================================================
[   35.284710] BUG: KASAN: use-after-free in ext4_xattr_delete_inode+0xa33/0xa70
[   35.285676] Read of size 4 at addr ffff88800aaee000 by task repro/188
[   35.286694]
[   35.286912] CPU: 4 UID: 0 PID: 188 Comm: repro Not tainted 6.13.0-rc1+ #281
[   35.287560] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[   35.288709] Call Trace:
[   35.289368]  <TASK>
[   35.289790]  dump_stack_lvl+0x68/0xa0
[   35.290621]  print_report+0xcb/0x620
[   35.291400]  ? __virt_addr_valid+0x222/0x400
[   35.292325]  ? ext4_xattr_delete_inode+0xa33/0xa70
[   35.293198]  kasan_report+0xbd/0xf0
[   35.293738]  ? ext4_xattr_delete_inode+0xa33/0xa70
[   35.294461]  ext4_xattr_delete_inode+0xa33/0xa70
[   35.295200]  ? __pfx_ext4_xattr_delete_inode+0x10/0x10
[   35.295975]  ? __ext4_journal_start_sb+0x7b/0x520
[   35.296849]  ? lock_is_held_type+0x9e/0x120
[   35.297711]  ext4_evict_inode+0x64b/0x14f0
[   35.298225]  ? __pfx_lock_release+0x10/0x10
[   35.298724]  ? do_raw_spin_lock+0x131/0x270
[   35.299256]  ? __pfx_ext4_evict_inode+0x10/0x10
[   35.299824]  ? __pfx_do_raw_spin_lock+0x10/0x10
[   35.300593]  evict+0x334/0x790
[   35.300991]  ? __pfx_evict+0x10/0x10
[   35.301890]  ? do_raw_spin_unlock+0x58/0x220
[   35.302817]  ? _raw_spin_unlock+0x23/0x40
[   35.303676]  ? iput+0x441/0x610
[   35.304218]  vfs_rmdir+0x44b/0x5a0
[   35.304769]  ? lookup_one_qstr_excl+0x24/0x150
[   35.305297]  do_rmdir+0x28e/0x370
[   35.305678]  ? __pfx_do_rmdir+0x10/0x10
[   35.306148]  ? trace_kmem_cache_alloc+0x24/0xb0
[   35.306703]  ? getname_flags+0xb3/0x410
[   35.307190]  __x64_sys_rmdir+0x40/0x50
[   35.307641]  do_syscall_64+0xc1/0x1d0
[   35.308096]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[   35.308701] RIP: 0033:0x7f071988381b
[   35.309147] Code: f0 ff ff 73 01 c3 48 8b 0d 02 36 0e 00 f7 d8 64 89 01 48 83 c8 ff c3 0f 1f 84 00 00 00 00 00 f3 0f 1e fa b8 54 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 05 c3 0f 1f 40 00 48 8b 15 d1 35 0e 00 f7 d8
[   35.311700] RSP: 002b:00007ffc6ed5f4f8 EFLAGS: 00000202 ORIG_RAX: 0000000000000054
[   35.313089] RAX: ffffffffffffffda RBX: 00007ffc6ed62948 RCX: 00007f071988381b
[   35.314271] RDX: 0000000000000000 RSI: 000056215a1fd2e0 RDI: 00007ffc6ed606c0
[   35.315464] RBP: 00007ffc6ed605e0 R08: 0000000000000073 R09: 0000000000000000
[   35.316537] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000000
[   35.317452] R13: 00007ffc6ed62958 R14: 00005621223ddc70 R15: 00007f07199d6000
[   35.318345]  </TASK>
[   35.318625]
[   35.318827] The buggy address belongs to the physical page:
[   35.319896] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x67 pfn:0xaaee
[   35.320879] flags: 0x80000000000000(node=0|zone=1)
[   35.321361] raw: 0080000000000000 ffffea00002abbc8 ffffea00002ac348 0000000000000000
[   35.322478] raw: 0000000000000067 0000000000000000 00000000ffffffff 0000000000000000
[   35.323460] page dumped because: kasan: bad access detected
[   35.324149]
[   35.324347] Memory state around the buggy address:
[   35.324958]  ffff88800aaedf00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[   35.326113]  ffff88800aaedf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[   35.327190] >ffff88800aaee000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   35.328162]                    ^
[   35.328700]  ffff88800aaee080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   35.329505]  ffff88800aaee100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   35.330516] ==================================================================
[   35.332287] Disabling lock debugging due to kernel taint

Reported-by: syzbot+57934e2c8e7a99992e41@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=57934e2c8e7a99992e41
Fixes: 6dd4ee7cab7e ("ext4: Expand extra_inodes space per the s_{want,min}_extra_isize fields")
Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@igalia.com>
---
 fs/ext4/inode.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 89aade6f45f6..ed32c26abbfd 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5867,8 +5867,7 @@ static int __ext4_expand_extra_isize(struct inode *inode,
 	header = IHDR(inode, raw_inode);
 
 	/* No extended attributes present */
-	if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR) ||
-	    header->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC)) {
+	if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) {
 		memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
 		       EXT4_I(inode)->i_extra_isize, 0,
 		       new_extra_isize - EXT4_I(inode)->i_extra_isize);
-- 
2.34.1
Re: [PATCH] ext4: only test for inode xattr state when expanding inode
Posted by kernel test robot 1 year ago
Hi Thadeu,

kernel test robot noticed the following build warnings:

[auto build test WARNING on tytso-ext4/dev]
[also build test WARNING on linus/master v6.13-rc2 next-20241210]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Thadeu-Lima-de-Souza-Cascardo/ext4-only-test-for-inode-xattr-state-when-expanding-inode/20241211-015015
base:   https://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4.git dev
patch link:    https://lore.kernel.org/r/20241210174850.4027690-1-cascardo%40igalia.com
patch subject: [PATCH] ext4: only test for inode xattr state when expanding inode
config: csky-randconfig-002-20241211 (https://download.01.org/0day-ci/archive/20241211/202412111225.cNzuFVRM-lkp@intel.com/config)
compiler: csky-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241211/202412111225.cNzuFVRM-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202412111225.cNzuFVRM-lkp@intel.com/

All warnings (new ones prefixed by >>):

   fs/ext4/inode.c: In function '__ext4_expand_extra_isize':
>> fs/ext4/inode.c:5818:41: warning: variable 'header' set but not used [-Wunused-but-set-variable]
    5818 |         struct ext4_xattr_ibody_header *header;
         |                                         ^~~~~~


vim +/header +5818 fs/ext4/inode.c

ac27a0ec112a089 Dave Kleikamp                 2006-10-11  5811  
c03b45b853f5829 Miao Xie                      2017-08-06  5812  static int __ext4_expand_extra_isize(struct inode *inode,
c03b45b853f5829 Miao Xie                      2017-08-06  5813  				     unsigned int new_extra_isize,
c03b45b853f5829 Miao Xie                      2017-08-06  5814  				     struct ext4_iloc *iloc,
c03b45b853f5829 Miao Xie                      2017-08-06  5815  				     handle_t *handle, int *no_expand)
c03b45b853f5829 Miao Xie                      2017-08-06  5816  {
c03b45b853f5829 Miao Xie                      2017-08-06  5817  	struct ext4_inode *raw_inode;
c03b45b853f5829 Miao Xie                      2017-08-06 @5818  	struct ext4_xattr_ibody_header *header;
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5819  	unsigned int inode_size = EXT4_INODE_SIZE(inode->i_sb);
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5820  	struct ext4_inode_info *ei = EXT4_I(inode);
c03b45b853f5829 Miao Xie                      2017-08-06  5821  	int error;
c03b45b853f5829 Miao Xie                      2017-08-06  5822  
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5823  	/* this was checked at iget time, but double check for good measure */
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5824  	if ((EXT4_GOOD_OLD_INODE_SIZE + ei->i_extra_isize > inode_size) ||
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5825  	    (ei->i_extra_isize & 3)) {
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5826  		EXT4_ERROR_INODE(inode, "bad extra_isize %u (inode size %u)",
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5827  				 ei->i_extra_isize,
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5828  				 EXT4_INODE_SIZE(inode->i_sb));
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5829  		return -EFSCORRUPTED;
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5830  	}
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5831  	if ((new_extra_isize < ei->i_extra_isize) ||
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5832  	    (new_extra_isize < 4) ||
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5833  	    (new_extra_isize > inode_size - EXT4_GOOD_OLD_INODE_SIZE))
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5834  		return -EINVAL;	/* Should never happen */
4ea99936a1630f5 Theodore Ts'o                 2019-11-07  5835  
c03b45b853f5829 Miao Xie                      2017-08-06  5836  	raw_inode = ext4_raw_inode(iloc);
c03b45b853f5829 Miao Xie                      2017-08-06  5837  
c03b45b853f5829 Miao Xie                      2017-08-06  5838  	header = IHDR(inode, raw_inode);
c03b45b853f5829 Miao Xie                      2017-08-06  5839  
c03b45b853f5829 Miao Xie                      2017-08-06  5840  	/* No extended attributes present */
555d75b1e3bf941 Thadeu Lima de Souza Cascardo 2024-12-10  5841  	if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) {
c03b45b853f5829 Miao Xie                      2017-08-06  5842  		memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
c03b45b853f5829 Miao Xie                      2017-08-06  5843  		       EXT4_I(inode)->i_extra_isize, 0,
c03b45b853f5829 Miao Xie                      2017-08-06  5844  		       new_extra_isize - EXT4_I(inode)->i_extra_isize);
c03b45b853f5829 Miao Xie                      2017-08-06  5845  		EXT4_I(inode)->i_extra_isize = new_extra_isize;
c03b45b853f5829 Miao Xie                      2017-08-06  5846  		return 0;
c03b45b853f5829 Miao Xie                      2017-08-06  5847  	}
c03b45b853f5829 Miao Xie                      2017-08-06  5848  
8994d11395f8165 Jan Kara                      2022-12-07  5849  	/*
8994d11395f8165 Jan Kara                      2022-12-07  5850  	 * We may need to allocate external xattr block so we need quotas
8994d11395f8165 Jan Kara                      2022-12-07  5851  	 * initialized. Here we can be called with various locks held so we
8994d11395f8165 Jan Kara                      2022-12-07  5852  	 * cannot affort to initialize quotas ourselves. So just bail.
8994d11395f8165 Jan Kara                      2022-12-07  5853  	 */
8994d11395f8165 Jan Kara                      2022-12-07  5854  	if (dquot_initialize_needed(inode))
8994d11395f8165 Jan Kara                      2022-12-07  5855  		return -EAGAIN;
8994d11395f8165 Jan Kara                      2022-12-07  5856  
c03b45b853f5829 Miao Xie                      2017-08-06  5857  	/* try to expand with EAs present */
c03b45b853f5829 Miao Xie                      2017-08-06  5858  	error = ext4_expand_extra_isize_ea(inode, new_extra_isize,
c03b45b853f5829 Miao Xie                      2017-08-06  5859  					   raw_inode, handle);
c03b45b853f5829 Miao Xie                      2017-08-06  5860  	if (error) {
c03b45b853f5829 Miao Xie                      2017-08-06  5861  		/*
c03b45b853f5829 Miao Xie                      2017-08-06  5862  		 * Inode size expansion failed; don't try again
c03b45b853f5829 Miao Xie                      2017-08-06  5863  		 */
c03b45b853f5829 Miao Xie                      2017-08-06  5864  		*no_expand = 1;
c03b45b853f5829 Miao Xie                      2017-08-06  5865  	}
c03b45b853f5829 Miao Xie                      2017-08-06  5866  
c03b45b853f5829 Miao Xie                      2017-08-06  5867  	return error;
c03b45b853f5829 Miao Xie                      2017-08-06  5868  }
c03b45b853f5829 Miao Xie                      2017-08-06  5869  

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
[PATCH v2] ext4: only test for inode xattr state when expanding inode
Posted by Thadeu Lima de Souza Cascardo 1 year ago
When expanding inode size, a check for the xattr magic code could fail
because the underlying data was corrupt or changed by directly writing to
the block device.

But instead of detecting such corruption, the current test would clear the
data but keep the EXT4_STATE_XATTR bit set in the inode state.

When later deleting the inode, this would lead for a test for such bit to
succeed and then an out-of-bounds access.

Since the state could only be set when the magic code has been detected
(and when such bit is cleared, so is the magic code), it is sufficient to
test for such state when deciding whether expanding the inode size is safe.

Here is the KASAN report.

[   35.283769] ==================================================================
[   35.284710] BUG: KASAN: use-after-free in ext4_xattr_delete_inode+0xa33/0xa70
[   35.285676] Read of size 4 at addr ffff88800aaee000 by task repro/188
[   35.286694]
[   35.286912] CPU: 4 UID: 0 PID: 188 Comm: repro Not tainted 6.13.0-rc1+ #281
[   35.287560] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[   35.288709] Call Trace:
[   35.289368]  <TASK>
[   35.289790]  dump_stack_lvl+0x68/0xa0
[   35.290621]  print_report+0xcb/0x620
[   35.291400]  ? __virt_addr_valid+0x222/0x400
[   35.292325]  ? ext4_xattr_delete_inode+0xa33/0xa70
[   35.293198]  kasan_report+0xbd/0xf0
[   35.293738]  ? ext4_xattr_delete_inode+0xa33/0xa70
[   35.294461]  ext4_xattr_delete_inode+0xa33/0xa70
[   35.295200]  ? __pfx_ext4_xattr_delete_inode+0x10/0x10
[   35.295975]  ? __ext4_journal_start_sb+0x7b/0x520
[   35.296849]  ? lock_is_held_type+0x9e/0x120
[   35.297711]  ext4_evict_inode+0x64b/0x14f0
[   35.298225]  ? __pfx_lock_release+0x10/0x10
[   35.298724]  ? do_raw_spin_lock+0x131/0x270
[   35.299256]  ? __pfx_ext4_evict_inode+0x10/0x10
[   35.299824]  ? __pfx_do_raw_spin_lock+0x10/0x10
[   35.300593]  evict+0x334/0x790
[   35.300991]  ? __pfx_evict+0x10/0x10
[   35.301890]  ? do_raw_spin_unlock+0x58/0x220
[   35.302817]  ? _raw_spin_unlock+0x23/0x40
[   35.303676]  ? iput+0x441/0x610
[   35.304218]  vfs_rmdir+0x44b/0x5a0
[   35.304769]  ? lookup_one_qstr_excl+0x24/0x150
[   35.305297]  do_rmdir+0x28e/0x370
[   35.305678]  ? __pfx_do_rmdir+0x10/0x10
[   35.306148]  ? trace_kmem_cache_alloc+0x24/0xb0
[   35.306703]  ? getname_flags+0xb3/0x410
[   35.307190]  __x64_sys_rmdir+0x40/0x50
[   35.307641]  do_syscall_64+0xc1/0x1d0
[   35.308096]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[   35.308701] RIP: 0033:0x7f071988381b
[   35.309147] Code: f0 ff ff 73 01 c3 48 8b 0d 02 36 0e 00 f7 d8 64 89 01 48 83 c8 ff c3 0f 1f 84 00 00 00 00 00 f3 0f 1e fa b8 54 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 05 c3 0f 1f 40 00 48 8b 15 d1 35 0e 00 f7 d8
[   35.311700] RSP: 002b:00007ffc6ed5f4f8 EFLAGS: 00000202 ORIG_RAX: 0000000000000054
[   35.313089] RAX: ffffffffffffffda RBX: 00007ffc6ed62948 RCX: 00007f071988381b
[   35.314271] RDX: 0000000000000000 RSI: 000056215a1fd2e0 RDI: 00007ffc6ed606c0
[   35.315464] RBP: 00007ffc6ed605e0 R08: 0000000000000073 R09: 0000000000000000
[   35.316537] R10: 0000000000000000 R11: 0000000000000202 R12: 0000000000000000
[   35.317452] R13: 00007ffc6ed62958 R14: 00005621223ddc70 R15: 00007f07199d6000
[   35.318345]  </TASK>
[   35.318625]
[   35.318827] The buggy address belongs to the physical page:
[   35.319896] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x67 pfn:0xaaee
[   35.320879] flags: 0x80000000000000(node=0|zone=1)
[   35.321361] raw: 0080000000000000 ffffea00002abbc8 ffffea00002ac348 0000000000000000
[   35.322478] raw: 0000000000000067 0000000000000000 00000000ffffffff 0000000000000000
[   35.323460] page dumped because: kasan: bad access detected
[   35.324149]
[   35.324347] Memory state around the buggy address:
[   35.324958]  ffff88800aaedf00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[   35.326113]  ffff88800aaedf80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[   35.327190] >ffff88800aaee000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   35.328162]                    ^
[   35.328700]  ffff88800aaee080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   35.329505]  ffff88800aaee100: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
[   35.330516] ==================================================================
[   35.332287] Disabling lock debugging due to kernel taint

Reported-by: syzbot+57934e2c8e7a99992e41@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=57934e2c8e7a99992e41
Fixes: 6dd4ee7cab7e ("ext4: Expand extra_inodes space per the s_{want,min}_extra_isize fields")
Signed-off-by: Thadeu Lima de Souza Cascardo <cascardo@igalia.com>
---
 fs/ext4/inode.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 89aade6f45f6..38a1012d4a14 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -5844,7 +5844,6 @@ static int __ext4_expand_extra_isize(struct inode *inode,
 				     handle_t *handle, int *no_expand)
 {
 	struct ext4_inode *raw_inode;
-	struct ext4_xattr_ibody_header *header;
 	unsigned int inode_size = EXT4_INODE_SIZE(inode->i_sb);
 	struct ext4_inode_info *ei = EXT4_I(inode);
 	int error;
@@ -5864,11 +5863,8 @@ static int __ext4_expand_extra_isize(struct inode *inode,
 
 	raw_inode = ext4_raw_inode(iloc);
 
-	header = IHDR(inode, raw_inode);
-
 	/* No extended attributes present */
-	if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR) ||
-	    header->h_magic != cpu_to_le32(EXT4_XATTR_MAGIC)) {
+	if (!ext4_test_inode_state(inode, EXT4_STATE_XATTR)) {
 		memset((void *)raw_inode + EXT4_GOOD_OLD_INODE_SIZE +
 		       EXT4_I(inode)->i_extra_isize, 0,
 		       new_extra_isize - EXT4_I(inode)->i_extra_isize);
-- 
2.34.1