linux-ext4.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/3] Check content after reading from quota file
@ 2022-09-23 13:45 Zhihao Cheng
  2022-09-23 13:45 ` [PATCH v3 1/3] quota: Check next/prev free block number " Zhihao Cheng
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Zhihao Cheng @ 2022-09-23 13:45 UTC (permalink / raw)
  To: jack, tytso, brauner
  Cc: linux-fsdevel, linux-ext4, linux-kernel, chengzhihao1, yukuai3

v1->v2:
  Add CC-stable tag in first patch.
  Rename check_free_block() -> check_dquot_block_header().
v2->v3:
  Define do_check_range() completely in patch 1.
  Move 'dqdh_entries' checking into check_dquot_block_header().
  Do block checing later in find_next_id().

Zhihao Cheng (3):
  quota: Check next/prev free block number after reading from quota file
  quota: Replace all block number checking with helper function
  quota: Add more checking after reading from quota file

 fs/quota/quota_tree.c | 73 ++++++++++++++++++++++++++++++++++++-------
 1 file changed, 61 insertions(+), 12 deletions(-)

-- 
2.31.1


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 1/3] quota: Check next/prev free block number after reading from quota file
  2022-09-23 13:45 [PATCH v3 0/3] Check content after reading from quota file Zhihao Cheng
@ 2022-09-23 13:45 ` Zhihao Cheng
  2022-09-23 13:45 ` [PATCH v3 2/3] quota: Replace all block number checking with helper function Zhihao Cheng
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Zhihao Cheng @ 2022-09-23 13:45 UTC (permalink / raw)
  To: jack, tytso, brauner
  Cc: linux-fsdevel, linux-ext4, linux-kernel, chengzhihao1, yukuai3

Following process:
 Init: v2_read_file_info: <3> dqi_free_blk 0 dqi_free_entry 5 dqi_blks 6

 Step 1. chown bin f_a -> dquot_acquire -> v2_write_dquot:
  qtree_write_dquot
   do_insert_tree
    find_free_dqentry
     get_free_dqblk
      write_blk(info->dqi_blocks) // info->dqi_blocks = 6, failure. The
	   content in physical block (corresponding to blk 6) is random.

 Step 2. chown root f_a -> dquot_transfer -> dqput_all -> dqput ->
         ext4_release_dquot -> v2_release_dquot -> qtree_delete_dquot:
  dquot_release
   remove_tree
    free_dqentry
     put_free_dqblk(6)
      info->dqi_free_blk = blk    // info->dqi_free_blk = 6

 Step 3. drop cache (buffer head for block 6 is released)

 Step 4. chown bin f_b -> dquot_acquire -> commit_dqblk -> v2_write_dquot:
  qtree_write_dquot
   do_insert_tree
    find_free_dqentry
     get_free_dqblk
      dh = (struct qt_disk_dqdbheader *)buf
      blk = info->dqi_free_blk     // 6
      ret = read_blk(info, blk, buf)  // The content of buf is random
      info->dqi_free_blk = le32_to_cpu(dh->dqdh_next_free)  // random blk

 Step 5. chown bin f_c -> notify_change -> ext4_setattr -> dquot_transfer:
  dquot = dqget -> acquire_dquot -> ext4_acquire_dquot -> dquot_acquire ->
          commit_dqblk -> v2_write_dquot -> dq_insert_tree:
   do_insert_tree
    find_free_dqentry
     get_free_dqblk
      blk = info->dqi_free_blk    // If blk < 0 and blk is not an error
				     code, it will be returned as dquot

  transfer_to[USRQUOTA] = dquot  // A random negative value
  __dquot_transfer(transfer_to)
   dquot_add_inodes(transfer_to[cnt])
    spin_lock(&dquot->dq_dqb_lock)  // page fault

, which will lead to kernel page fault:
 Quota error (device sda): qtree_write_dquot: Error -8000 occurred
 while creating quota
 BUG: unable to handle page fault for address: ffffffffffffe120
 #PF: supervisor write access in kernel mode
 #PF: error_code(0x0002) - not-present page
 Oops: 0002 [#1] PREEMPT SMP
 CPU: 0 PID: 5974 Comm: chown Not tainted 6.0.0-rc1-00004
 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)
 RIP: 0010:_raw_spin_lock+0x3a/0x90
 Call Trace:
  dquot_add_inodes+0x28/0x270
  __dquot_transfer+0x377/0x840
  dquot_transfer+0xde/0x540
  ext4_setattr+0x405/0x14d0
  notify_change+0x68e/0x9f0
  chown_common+0x300/0x430
  __x64_sys_fchownat+0x29/0x40

In order to avoid accessing invalid quota memory address, this patch adds
block number checking of next/prev free block read from quota file.

Fetch a reproducer in [Link].

Link: https://bugzilla.kernel.org/show_bug.cgi?id=216372
Fixes: 1da177e4c3f4152 ("Linux-2.6.12-rc2")
CC: stable@vger.kernel.org
Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com>
---
 fs/quota/quota_tree.c | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/fs/quota/quota_tree.c b/fs/quota/quota_tree.c
index 5f2405994280..7e65d67de9f3 100644
--- a/fs/quota/quota_tree.c
+++ b/fs/quota/quota_tree.c
@@ -71,6 +71,35 @@ static ssize_t write_blk(struct qtree_mem_dqinfo *info, uint blk, char *buf)
 	return ret;
 }
 
+static inline int do_check_range(struct super_block *sb, const char *val_name,
+				 uint val, uint min_val, uint max_val)
+{
+	if (val < min_val || val > max_val) {
+		quota_error(sb, "Getting %s %u out of range %u-%u",
+			    val_name, val, min_val, max_val);
+		return -EUCLEAN;
+	}
+
+	return 0;
+}
+
+static int check_dquot_block_header(struct qtree_mem_dqinfo *info,
+				    struct qt_disk_dqdbheader *dh)
+{
+	int err = 0;
+
+	err = do_check_range(info->dqi_sb, "dqdh_next_free",
+			     le32_to_cpu(dh->dqdh_next_free), 0,
+			     info->dqi_blocks - 1);
+	if (err)
+		return err;
+	err = do_check_range(info->dqi_sb, "dqdh_prev_free",
+			     le32_to_cpu(dh->dqdh_prev_free), 0,
+			     info->dqi_blocks - 1);
+
+	return err;
+}
+
 /* Remove empty block from list and return it */
 static int get_free_dqblk(struct qtree_mem_dqinfo *info)
 {
@@ -85,6 +114,9 @@ static int get_free_dqblk(struct qtree_mem_dqinfo *info)
 		ret = read_blk(info, blk, buf);
 		if (ret < 0)
 			goto out_buf;
+		ret = check_dquot_block_header(info, dh);
+		if (ret)
+			goto out_buf;
 		info->dqi_free_blk = le32_to_cpu(dh->dqdh_next_free);
 	}
 	else {
@@ -232,6 +264,9 @@ static uint find_free_dqentry(struct qtree_mem_dqinfo *info,
 		*err = read_blk(info, blk, buf);
 		if (*err < 0)
 			goto out_buf;
+		*err = check_dquot_block_header(info, dh);
+		if (*err)
+			goto out_buf;
 	} else {
 		blk = get_free_dqblk(info);
 		if ((int)blk < 0) {
@@ -424,6 +459,9 @@ static int free_dqentry(struct qtree_mem_dqinfo *info, struct dquot *dquot,
 		goto out_buf;
 	}
 	dh = (struct qt_disk_dqdbheader *)buf;
+	ret = check_dquot_block_header(info, dh);
+	if (ret)
+		goto out_buf;
 	le16_add_cpu(&dh->dqdh_entries, -1);
 	if (!le16_to_cpu(dh->dqdh_entries)) {	/* Block got free? */
 		ret = remove_free_dqentry(info, buf, blk);
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 2/3] quota: Replace all block number checking with helper function
  2022-09-23 13:45 [PATCH v3 0/3] Check content after reading from quota file Zhihao Cheng
  2022-09-23 13:45 ` [PATCH v3 1/3] quota: Check next/prev free block number " Zhihao Cheng
@ 2022-09-23 13:45 ` Zhihao Cheng
  2022-09-23 13:45 ` [PATCH v3 3/3] quota: Add more checking after reading from quota file Zhihao Cheng
  2022-09-29 13:39 ` [PATCH v3 0/3] Check content " Jan Kara
  3 siblings, 0 replies; 5+ messages in thread
From: Zhihao Cheng @ 2022-09-23 13:45 UTC (permalink / raw)
  To: jack, tytso, brauner
  Cc: linux-fsdevel, linux-ext4, linux-kernel, chengzhihao1, yukuai3

Cleanup all block checking places, replace them with helper function
do_check_range().

Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com>
---
 fs/quota/quota_tree.c | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/fs/quota/quota_tree.c b/fs/quota/quota_tree.c
index 7e65d67de9f3..0fa73ca28045 100644
--- a/fs/quota/quota_tree.c
+++ b/fs/quota/quota_tree.c
@@ -518,12 +518,10 @@ static int remove_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
 		goto out_buf;
 	}
 	newblk = le32_to_cpu(ref[get_index(info, dquot->dq_id, depth)]);
-	if (newblk < QT_TREEOFF || newblk >= info->dqi_blocks) {
-		quota_error(dquot->dq_sb, "Getting block too big (%u >= %u)",
-			    newblk, info->dqi_blocks);
-		ret = -EUCLEAN;
+	ret = do_check_range(dquot->dq_sb, "block", newblk, QT_TREEOFF,
+			     info->dqi_blocks - 1);
+	if (ret)
 		goto out_buf;
-	}
 
 	if (depth == info->dqi_qtree_depth - 1) {
 		ret = free_dqentry(info, dquot, newblk);
@@ -624,12 +622,10 @@ static loff_t find_tree_dqentry(struct qtree_mem_dqinfo *info,
 	blk = le32_to_cpu(ref[get_index(info, dquot->dq_id, depth)]);
 	if (!blk)	/* No reference? */
 		goto out_buf;
-	if (blk < QT_TREEOFF || blk >= info->dqi_blocks) {
-		quota_error(dquot->dq_sb, "Getting block too big (%u >= %u)",
-			    blk, info->dqi_blocks);
-		ret = -EUCLEAN;
+	ret = do_check_range(dquot->dq_sb, "block", blk, QT_TREEOFF,
+			     info->dqi_blocks - 1);
+	if (ret)
 		goto out_buf;
-	}
 
 	if (depth < info->dqi_qtree_depth - 1)
 		ret = find_tree_dqentry(info, dquot, blk, depth+1);
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 3/3] quota: Add more checking after reading from quota file
  2022-09-23 13:45 [PATCH v3 0/3] Check content after reading from quota file Zhihao Cheng
  2022-09-23 13:45 ` [PATCH v3 1/3] quota: Check next/prev free block number " Zhihao Cheng
  2022-09-23 13:45 ` [PATCH v3 2/3] quota: Replace all block number checking with helper function Zhihao Cheng
@ 2022-09-23 13:45 ` Zhihao Cheng
  2022-09-29 13:39 ` [PATCH v3 0/3] Check content " Jan Kara
  3 siblings, 0 replies; 5+ messages in thread
From: Zhihao Cheng @ 2022-09-23 13:45 UTC (permalink / raw)
  To: jack, tytso, brauner
  Cc: linux-fsdevel, linux-ext4, linux-kernel, chengzhihao1, yukuai3

It would be better to do more sanity checking (eg. dqdh_entries,
block no.) for the content read from quota file, which can prevent
corrupting the quota file.

Signed-off-by: Zhihao Cheng <chengzhihao1@huawei.com>
---
 fs/quota/quota_tree.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/fs/quota/quota_tree.c b/fs/quota/quota_tree.c
index 0fa73ca28045..0f1493e0f6d0 100644
--- a/fs/quota/quota_tree.c
+++ b/fs/quota/quota_tree.c
@@ -96,6 +96,11 @@ static int check_dquot_block_header(struct qtree_mem_dqinfo *info,
 	err = do_check_range(info->dqi_sb, "dqdh_prev_free",
 			     le32_to_cpu(dh->dqdh_prev_free), 0,
 			     info->dqi_blocks - 1);
+	if (err)
+		return err;
+	err = do_check_range(info->dqi_sb, "dqdh_entries",
+			     le16_to_cpu(dh->dqdh_entries), 0,
+			     qtree_dqstr_in_blk(info));
 
 	return err;
 }
@@ -348,6 +353,10 @@ static int do_insert_tree(struct qtree_mem_dqinfo *info, struct dquot *dquot,
 	}
 	ref = (__le32 *)buf;
 	newblk = le32_to_cpu(ref[get_index(info, dquot->dq_id, depth)]);
+	ret = do_check_range(dquot->dq_sb, "block", newblk, 0,
+			     info->dqi_blocks - 1);
+	if (ret)
+		goto out_buf;
 	if (!newblk)
 		newson = 1;
 	if (depth == info->dqi_qtree_depth - 1) {
@@ -739,15 +748,21 @@ static int find_next_id(struct qtree_mem_dqinfo *info, qid_t *id,
 		goto out_buf;
 	}
 	for (i = __get_index(info, *id, depth); i < epb; i++) {
-		if (ref[i] == cpu_to_le32(0)) {
+		uint blk_no = le32_to_cpu(ref[i]);
+
+		if (blk_no == 0) {
 			*id += level_inc;
 			continue;
 		}
+		ret = do_check_range(info->dqi_sb, "block", blk_no, 0,
+				     info->dqi_blocks - 1);
+		if (ret)
+			goto out_buf;
 		if (depth == info->dqi_qtree_depth - 1) {
 			ret = 0;
 			goto out_buf;
 		}
-		ret = find_next_id(info, id, le32_to_cpu(ref[i]), depth + 1);
+		ret = find_next_id(info, id, blk_no, depth + 1);
 		if (ret != -ENOENT)
 			break;
 	}
-- 
2.31.1


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 0/3] Check content after reading from quota file
  2022-09-23 13:45 [PATCH v3 0/3] Check content after reading from quota file Zhihao Cheng
                   ` (2 preceding siblings ...)
  2022-09-23 13:45 ` [PATCH v3 3/3] quota: Add more checking after reading from quota file Zhihao Cheng
@ 2022-09-29 13:39 ` Jan Kara
  3 siblings, 0 replies; 5+ messages in thread
From: Jan Kara @ 2022-09-29 13:39 UTC (permalink / raw)
  To: Zhihao Cheng
  Cc: jack, tytso, brauner, linux-fsdevel, linux-ext4, linux-kernel, yukuai3

On Fri 23-09-22 21:45:51, Zhihao Cheng wrote:
> v1->v2:
>   Add CC-stable tag in first patch.
>   Rename check_free_block() -> check_dquot_block_header().
> v2->v3:
>   Define do_check_range() completely in patch 1.
>   Move 'dqdh_entries' checking into check_dquot_block_header().
>   Do block checing later in find_next_id().
> 
> Zhihao Cheng (3):
>   quota: Check next/prev free block number after reading from quota file
>   quota: Replace all block number checking with helper function
>   quota: Add more checking after reading from quota file
> 
>  fs/quota/quota_tree.c | 73 ++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 61 insertions(+), 12 deletions(-)

Thanks! I've merged the patches to my tree now!

								Honza
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2022-09-29 13:39 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-23 13:45 [PATCH v3 0/3] Check content after reading from quota file Zhihao Cheng
2022-09-23 13:45 ` [PATCH v3 1/3] quota: Check next/prev free block number " Zhihao Cheng
2022-09-23 13:45 ` [PATCH v3 2/3] quota: Replace all block number checking with helper function Zhihao Cheng
2022-09-23 13:45 ` [PATCH v3 3/3] quota: Add more checking after reading from quota file Zhihao Cheng
2022-09-29 13:39 ` [PATCH v3 0/3] Check content " Jan Kara

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).