diff mbox series

block: rename 'q->debugfs_dir' in blk_unregister_queue()

Message ID 20200211035137.19454-1-yukuai3@huawei.com (mailing list archive)
State New, archived
Headers show
Series block: rename 'q->debugfs_dir' in blk_unregister_queue() | expand

Commit Message

Yu Kuai Feb. 11, 2020, 3:51 a.m. UTC
syzbot is reporting use after free bug in debugfs_remove[1].

This is because in request_queue, 'q->debugfs_dir' and
'q->blk_trace->dir' could be the same dir. And in __blk_release_queue(),
blk_mq_debugfs_unregister() will remove everything inside the dir.

With futher investigation of the reporduce repro, the problem can be
reporduced by following procedure:

1. LOOP_CTL_ADD, create a request_queue q1, blk_mq_debugfs_register() will
create the dir.
2. LOOP_CTL_REMOVE, blk_release_queue() will add q1 to release queue.
3. LOOP_CTL_ADD, create another request_queue q2,blk_mq_debugfs_register()
will fail because the dir aready exist.
4. BLKTRACESETUP, create two files(msg and dropped) inside the dir.
5. call __blk_release_queue() for q1, debugfs_remove_recursive() will
delete the files created in step 4.
6. LOOP_CTL_REMOVE, blk_release_queue() will add q2 to release queue.
And when __blk_release_queue() is called for q2, blk_trace_shutdown() will
try to release the two files created in step 4, wich are aready released
in step 5.

|thread1		  |kworker	             |thread2               |
| ----------------------- | ------------------------ | -------------------- |
|loop_control_ioctl       |                          |                      |
| loop_add                |                          |                      |
|  blk_mq_debugfs_register|                          |                      |
|   debugfs_create_dir    |                          |                      |
|loop_control_ioctl       |                          |                      |
| loop_remove		  |                          |                      |
|  blk_release_queue      |                          |                      |
|   schedule_work         |                          |                      |
|			  |			     |loop_control_ioctl    |
|			  |			     | loop_add             |
|			  |			     |  ...                 |
|			  |			     |blk_trace_ioctl       |
|			  |			     | __blk_trace_setup    |
|			  |			     |   debugfs_create_file|
|			  |__blk_release_queue       |                      |
|			  | blk_mq_debugfs_unregister|                      |
|			  |  debugfs_remove_recursive|                      |
|			  |			     |loop_control_ioctl    |
|			  |			     | loop_remove          |
|			  |			     |  ...                 |
|			  |__blk_release_queue       |                      |
|			  | blk_trace_shutdown       |                      |
|			  |  debugfs_remove          |                      |

commit dc9edc44de6c ("block: Fix a blk_exit_rl() regression") pushed the
final release of request_queue to a workqueue, so, when loop_add() is
called again(step 3), __blk_release_queue() might not been called yet,
which causes the problem.

Fix the problem by renaming 'q->debugfs_dir' in blk_unregister_queue().

[1] https://syzkaller.appspot.com/bug?extid=903b72a010ad6b7a40f2
References: CVE-2019-19770
Fixes: commit dc9edc44de6c ("block: Fix a blk_exit_rl() regression")
Reported-by: syzbot <syz...@syzkaller.appspotmail.com>
Signed-off-by: yu kuai <yukuai3@huawei.com>
---
 block/blk-sysfs.c | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

Comments

Bart Van Assche Feb. 12, 2020, 3:27 a.m. UTC | #1
On 2020-02-10 19:51, yu kuai wrote:
> +static struct dentry *blk_prepare_release_queue(struct request_queue *q)
> +{
> +	struct dentry *new = NULL;
> +	char name[DNAME_INLINE_LEN];
> +	int i = 0;
> +
> +	if (IS_ERR_OR_NULL(q->debugfs_dir))
> +		return q->debugfs_dir;
> +
> +	while (new == NULL) {
> +		sprintf(name, "ready_to_remove_%d", i++);
> +		new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
> +				     blk_debugfs_root, name);
> +	}
> +
> +	return new;
> +}

What is the behavior of this loop if multiple block devices are being
removed concurrently? Does it perhaps change remove block device removal
from an O(1) into an O(n) operation?

Since this scenario may only matter to syzbot tests: has it been
considered to delay block device creation if the debugfs directory from
a previous incarnation of the block device still exists?

Thanks,

Bart.
Yu Kuai Feb. 12, 2020, 4:20 a.m. UTC | #2
On 2020/2/12 11:27, Bart Van Assche wrote:
> What is the behavior of this loop if multiple block devices are being
> removed concurrently? Does it perhaps change remove block device removal
> from an O(1) into an O(n) operation?
Yes, there may be performance overhead.(I thought it's minimal) However,
I can change the name of dir form "read_to_remove_%d" to
"read_to_remove_%s(dev_name)_%d" to fix that.
> 
> Since this scenario may only matter to syzbot tests: has it been
> considered to delay block device creation if the debugfs directory from
> a previous incarnation of the block device still exists?
> 
I think it's a bug device creation succeed when the debugfs directory
exist. Of course delay block device creation can fix the problem, but I
haven't come up with a good solution. And by renaming the dir, there is
no need to delay cration.

Thanks!
Yu Kuai
kernel test robot Feb. 13, 2020, 4:38 a.m. UTC | #3
Hi yu,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on block/for-next]
[also build test ERROR on v5.6-rc1 next-20200212]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/yu-kuai/block-rename-q-debugfs_dir-in-blk_unregister_queue/20200213-091022
base:   https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git for-next
config: um-x86_64_defconfig (attached as .config)
compiler: gcc-7 (Debian 7.5.0-4) 7.5.0
reproduce:
        # save the attached .config to linux build tree
        make ARCH=um SUBARCH=x86_64

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   block/blk-sysfs.c: In function 'blk_prepare_release_queue':
>> block/blk-sysfs.c:1030:22: error: 'struct request_queue' has no member named 'debugfs_dir'
     if (IS_ERR_OR_NULL(q->debugfs_dir))
                         ^~
   block/blk-sysfs.c:1031:11: error: 'struct request_queue' has no member named 'debugfs_dir'
      return q->debugfs_dir;
              ^~
>> block/blk-sysfs.c:1035:24: error: 'blk_debugfs_root' undeclared (first use in this function); did you mean 'blk_mq_debugfs_attr'?
      new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
                           ^~~~~~~~~~~~~~~~
                           blk_mq_debugfs_attr
   block/blk-sysfs.c:1035:24: note: each undeclared identifier is reported only once for each function it appears in
   block/blk-sysfs.c:1035:43: error: 'struct request_queue' has no member named 'debugfs_dir'
      new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
                                              ^~
   block/blk-sysfs.c: In function 'blk_unregister_queue':
   block/blk-sysfs.c:1070:3: error: 'struct request_queue' has no member named 'debugfs_dir'
     q->debugfs_dir = blk_prepare_release_queue(q);
      ^~

vim +1030 block/blk-sysfs.c

  1014	
  1015	/*
  1016	 * blk_prepare_release_queue - rename q->debugfs_dir
  1017	 * @q: request_queue of which the dir to be renamed belong to.
  1018	 *
  1019	 * Because the final release of request_queue is in a workqueue, the
  1020	 * cleanup might not been finished yet while the same device start to
  1021	 * create. It's not correct if q->debugfs_dir still exist while trying
  1022	 * to create a new one.
  1023	 */
  1024	static struct dentry *blk_prepare_release_queue(struct request_queue *q)
  1025	{
  1026		struct dentry *new = NULL;
  1027		char name[DNAME_INLINE_LEN];
  1028		int i = 0;
  1029	
> 1030		if (IS_ERR_OR_NULL(q->debugfs_dir))
  1031			return q->debugfs_dir;
  1032	
  1033		while (new == NULL) {
  1034			sprintf(name, "ready_to_remove_%d", i++);
> 1035			new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
  1036					     blk_debugfs_root, name);
  1037		}
  1038	
  1039		return new;
  1040	}
  1041	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
kernel test robot Feb. 13, 2020, 11:39 a.m. UTC | #4
Hi yu,

Thank you for the patch! Yet something to improve:

[auto build test ERROR on block/for-next]
[also build test ERROR on v5.6-rc1 next-20200213]
[if your patch is applied to the wrong git tree, please drop us a note to help
improve the system. BTW, we also suggest to use '--base' option to specify the
base tree in git format-patch, please see https://stackoverflow.com/a/37406982]

url:    https://github.com/0day-ci/linux/commits/yu-kuai/block-rename-q-debugfs_dir-in-blk_unregister_queue/20200213-091022
base:   https://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux-block.git for-next
config: mips-fuloong2e_defconfig (attached as .config)
compiler: mips64el-linux-gcc (GCC) 5.5.0
reproduce:
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        GCC_VERSION=5.5.0 make.cross ARCH=mips 

If you fix the issue, kindly add following tag
Reported-by: kbuild test robot <lkp@intel.com>

All errors (new ones prefixed by >>):

   block/blk-sysfs.c: In function 'blk_prepare_release_queue':
   block/blk-sysfs.c:1030:22: error: 'struct request_queue' has no member named 'debugfs_dir'
     if (IS_ERR_OR_NULL(q->debugfs_dir))
                         ^
   block/blk-sysfs.c:1031:11: error: 'struct request_queue' has no member named 'debugfs_dir'
      return q->debugfs_dir;
              ^
>> block/blk-sysfs.c:1035:24: error: 'blk_debugfs_root' undeclared (first use in this function)
      new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
                           ^
   block/blk-sysfs.c:1035:24: note: each undeclared identifier is reported only once for each function it appears in
   block/blk-sysfs.c:1035:43: error: 'struct request_queue' has no member named 'debugfs_dir'
      new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
                                              ^
   block/blk-sysfs.c: In function 'blk_unregister_queue':
   block/blk-sysfs.c:1070:3: error: 'struct request_queue' has no member named 'debugfs_dir'
     q->debugfs_dir = blk_prepare_release_queue(q);
      ^

vim +/blk_debugfs_root +1035 block/blk-sysfs.c

  1014	
  1015	/*
  1016	 * blk_prepare_release_queue - rename q->debugfs_dir
  1017	 * @q: request_queue of which the dir to be renamed belong to.
  1018	 *
  1019	 * Because the final release of request_queue is in a workqueue, the
  1020	 * cleanup might not been finished yet while the same device start to
  1021	 * create. It's not correct if q->debugfs_dir still exist while trying
  1022	 * to create a new one.
  1023	 */
  1024	static struct dentry *blk_prepare_release_queue(struct request_queue *q)
  1025	{
  1026		struct dentry *new = NULL;
  1027		char name[DNAME_INLINE_LEN];
  1028		int i = 0;
  1029	
  1030		if (IS_ERR_OR_NULL(q->debugfs_dir))
  1031			return q->debugfs_dir;
  1032	
  1033		while (new == NULL) {
  1034			sprintf(name, "ready_to_remove_%d", i++);
> 1035			new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
  1036					     blk_debugfs_root, name);
  1037		}
  1038	
  1039		return new;
  1040	}
  1041	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c
index fca9b158f4a0..69d28b3f52d0 100644
--- a/block/blk-sysfs.c
+++ b/block/blk-sysfs.c
@@ -11,6 +11,7 @@ 
 #include <linux/blktrace_api.h>
 #include <linux/blk-mq.h>
 #include <linux/blk-cgroup.h>
+#include <linux/debugfs.h>
 
 #include "blk.h"
 #include "blk-mq.h"
@@ -1011,6 +1012,33 @@  int blk_register_queue(struct gendisk *disk)
 }
 EXPORT_SYMBOL_GPL(blk_register_queue);
 
+/*
+ * blk_prepare_release_queue - rename q->debugfs_dir
+ * @q: request_queue of which the dir to be renamed belong to.
+ *
+ * Because the final release of request_queue is in a workqueue, the
+ * cleanup might not been finished yet while the same device start to
+ * create. It's not correct if q->debugfs_dir still exist while trying
+ * to create a new one.
+ */
+static struct dentry *blk_prepare_release_queue(struct request_queue *q)
+{
+	struct dentry *new = NULL;
+	char name[DNAME_INLINE_LEN];
+	int i = 0;
+
+	if (IS_ERR_OR_NULL(q->debugfs_dir))
+		return q->debugfs_dir;
+
+	while (new == NULL) {
+		sprintf(name, "ready_to_remove_%d", i++);
+		new = debugfs_rename(blk_debugfs_root, q->debugfs_dir,
+				     blk_debugfs_root, name);
+	}
+
+	return new;
+}
+
 /**
  * blk_unregister_queue - counterpart of blk_register_queue()
  * @disk: Disk of which the request queue should be unregistered from sysfs.
@@ -1039,6 +1067,7 @@  void blk_unregister_queue(struct gendisk *disk)
 	mutex_unlock(&q->sysfs_lock);
 
 	mutex_lock(&q->sysfs_dir_lock);
+	q->debugfs_dir = blk_prepare_release_queue(q);
 	/*
 	 * Remove the sysfs attributes before unregistering the queue data
 	 * structures that can be modified through sysfs.