Linux Btrfs filesystem development
 help / color / mirror / Atom feed
From: Tao Cui <cui.tao@linux.dev>
To: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>, brauner@kernel.org
Cc: cui.tao@linux.dev, Markus.Elfring@web.de, axboe@kernel.dk,
	broonie@kernel.org, bvanassche@acm.org, dlemoal@kernel.org,
	hch@infradead.org, hch@lst.de, hdanton@sina.com,
	linux-block@vger.kernel.org, linux-btrfs@vger.kernel.org,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-next@vger.kernel.org, lkp@intel.com,
	oe-lkp@lists.linux.dev, oliver.sang@intel.com,
	torvalds@linux-foundation.org, viro@zeniv.linux.org.uk,
	Tao Cui <cuitao@kylinos.cn>
Subject: Re: [PATCH v7] loop: Fix NULL pointer dereference in lo_rw_aio()
Date: Tue, 1 Sep 2026 21:08:49 +0800	[thread overview]
Message-ID: <d1209b70-7e76-4415-92c9-6ddc192a3347@linux.dev> (raw)
In-Reply-To: <30528b67-6ff9-4512-892b-f48d819e100f@I-love.SAKURA.ne.jp>

Hi Bart, Tetsuo,

Thanks for the detailed replies, and to Tetsuo for the sashiko
log.

在 2026/8/31 23:49, Tetsuo Handa 写道:
> Thank you for joining to this thread, Tao.
> 
> On 2026/08/31 23:11, Tao Cui wrote:
>> From: Tao Cui <cuitao@kylinos.cn>
>>
>> Hi Tetsuo, Bart,
>>
>>> +	/* Step 1: Flush all outstanding I/O, without open_mutex held. */
>>> +	/*
>>> +	 * Now that loop_queue_rq() sees lo->lo_state != Lo_bound,
>>> +	 * wait for already started loop_queue_rq() to complete.
>>> +	 */
>>> +	synchronize_rcu();
>>
>> Your reply to Bart says loop_queue_rq() is called with RCU read
>> lock, but I don't see one.  The two call sites of ->queue_rq() are
>> blk_mq_dispatch_rq_list() and __blk_mq_issue_directly(), and
>> neither is wrapped in rcu_read_lock().  Direct issue from
>> blk_mq_submit_bio() runs in process context with no RCU read-side
>> critical section, so synchronize_rcu() does not wait for a
>> loop_queue_rq() that is already running there.  Only the softirq
>> dispatch path is an implicit RCU reader.
> 
> You can read sashiko's raw logs at https://sashiko.dev/#/log/49552 .
> 
>   Okay, let's break this down. My initial thought is to examine how the `blk_mq_run_dispatch_ops`
>   macro is implemented. Specifically, how does it handle RCU read locks? It wraps the provided
>   `dispatch_ops` in either `rcu_read_lock()` or `srcu_read_lock()`, depending on the `BLK_MQ_F_BLOCKING`
>   flag. The loop device, however, is set up *without* this `BLK_MQ_F_BLOCKING` flag. Therefore,
>   `loop_queue_rq` executes under the protection of `rcu_read_lock()`.
> 
>   Now, the critical question: Does the `synchronize_rcu()` call in `__loop_clr_fd` effectively address
>   the potential race conditions? Since `loop_queue_rq()` is indeed RCU-protected, `synchronize_rcu()` will
>   correctly wait for all currently executing instances of `loop_queue_rq()` to complete. Furthermore,
>   the `lo_state` transition to `Lo_rundown` before calling `__loop_clr_fd` ensures that any *new* calls to
>   `loop_queue_rq()` will see this state and return an error. This perfectly fences the execution of
>   `loop_queue_rq()`. Moreover, `drain_workqueue()` then makes sure all queued works are completed, and
>   these are scheduled by `loop_queue_rq()`. This is great; all scheduled works are completed before
>   clearing `lo->lo_backing_file`.
> 

Bart, on 8/31 you wrote:
> This is the code that protects .queue_rq() implementations like
> loop_queue_rq() with an rcu_read_lock() / rcu_read_unlock() pair:
>
> /* run the code block in @dispatch_ops with rcu/srcu read lock held */
> #define __blk_mq_run_dispatch_ops(q, check_sleep, dispatch_ops)	\
> ...
> 	} else {						\
> 		rcu_read_lock();				\
> 		(dispatch_ops);					\
> 		rcu_read_unlock();				\
> 	}							\
> } while (0)

You are both right and I was wrong.  I stopped at
__blk_mq_issue_directly() and blk_mq_dispatch_rq_list() and missed
that every caller goes through this macro.  So synchronize_rcu()
does wait for a running loop_queue_rq() with the current loop
setup.  I withdraw that comment.

>>
>> The window is still closed by the steps below, so this is not a
>> correctness bug, but the synchronize_rcu() is not doing what the
>> comment claims.  blk_mq_quiesce_queue() +
>> blk_mq_wait_quiesce_done(), as Bart suggested, would express the
>> intent directly.
> 
> synchronize_rcu() + drain_workqueue() can be implied by blk_mq_freeze_queue() + blk_mq_unfreeze_queue().
> But why are you talking about blk_mq_quiesce_queue() + blk_mq_wait_quiesce_done() ?
> 

That was Bart's suggestion from his earlier review, and I passed
it on while assuming the synchronize_rcu() was unfounded.  With
the RCU pairing confirmed, there is nothing left to replace and
the quiesce suggestion is moot.

>>
>>> +	/*
>>> +	 * Now that no more AIO requests are scheduled by lo_rw_aio(),
>>> +	 * wait for already started AIO to complete.
>>> +	 */
>>> +	blk_mq_unfreeze_queue(lo->lo_queue, blk_mq_freeze_queue(lo->lo_queue));
>>
>> About this step, in your follow-up you wrote:
>>
>>>   (1) since we are in lo_release() with disk_openers(disk) == 0, the activity of
>>>       incrementing/decrementing q_usage_counter (incremented before loop_queue_rq()
>>>       is called, and decremented after loop_queue_rq() returned BLK_STS_IOERR)) will
>>>       cease shortly
>>
>> The decrement timing here is only true for the error path.  For
>> BLK_STS_OK the reference is held until the request is freed, which
>> is what makes blk_mq_freeze_queue() wait for requests that already
>> passed the state check, including the loop workqueue worker that
>> completes them.  That is the property step 1 relies on, and it is
>> worth stating in the comment.
>>
>> What is still open is Bart's question about io_uring fixed files:
>> if submissions can continue after the last close, "cease shortly"
>> does not hold, and it is the freeze wait that actually drains them.
> 

The decrement part I would still reword: for BLK_STS_OK the
reference is held until the request is freed, and that is the
property blk_mq_freeze_queue() relies on.  But you are right that
the activity ceases, and for a simpler reason than the freeze:

> If submissions can continue _forever_ despite disk_openers(disk) == 0, what
> mechanism was preventing this problem from occurring until Linux 7.0 ?
> 

An io_uring fixed file holds a reference to the struct file, so
bd_openers never reaches zero and lo_release() does not start
while such a submission path exists.  Any submission needs an
open block device file (or a mount holding it), so "openers == 0
but submissions continue" needs no additional mechanism.  I'll
drop the io_uring part.

>>
>>> +	if (need_clear) {
>>> +		/*
>>> +		 * Grab all references that will be dropped as soon as
>>> +		 * returning from lo_release() and releasing disk->open_mutex.
>>> +		 */
>>> +		get_device(disk_to_dev(disk));
>>> +		__module_get(disk->fops->owner);
>>> +		queue_work(system_long_wq, &lo->lo_clr_work);
>>> +	}
>>
>> With teardown now asynchronous, between the last close()
>> returning and the work item finishing, lo_open() and
>> LOOP_CONFIGURE return -ENXIO.  That is the same behavior change
>> that led to the revert of the earlier attempt (bf23747ee053,
>> xfs/259).  Moving the xfstests side to the tests is one thing, but
>> userspace that closes a loop device and immediately reconfigures
>> it now needs to handle a transient -ENXIO.  Is that acceptable, or
>> should the retry happen in the kernel?
> 
> Does whether the teardown being synchronous or asynchronous matter so much?
> 
> I don't think we can control when e.g. udev-worker becomes the thread
> who actually calls __loop_clr_fd()
> ( https://lkml.kernel.org/r/9f8b5ab0-efbc-4cf3-a1f8-b43377416946@I-love.SAKURA.ne.jp ).
> Even if an existing user app calls close() immediately followed by open(),
> we can't prove that __loop_clr_fd() is synchronously called by that user app
> because udev-worker can jump in and udev-worker becomes the thread who actually
> calls __loop_clr_fd().
> 
>    An user app     udev-worker
>    -----------     -----------
>                     open()
>     close()
>     open() // => succeeds due to Lo_bound state
>     ioctl(LOOP_CONFIGURE) // => fails with -EBUSY due to Lo_bound state
>     close() // <= gives up due to ioctl() failure
>                     close() // => becomes  Lo_rundown and __loop_clr_fd() is called
> 
> A claim that mentions that the kernel is unable to release forever due to a refcount
> leak bug is valid. But a claim that mentions that the kernel cannot prove that
> this -ENXIO or -EBUSY problem never happens is invalid. Programs that use the loop
> device have to be prepared for transient errors.

Your udev-worker sequence shows that synchronous completion was
never guaranteed and -EBUSY or -ENXIO transients were already
possible.  I accept that.  The remaining difference is only that
the window now exists after every close instead of requiring an
interleaving close from another opener, and if programs must
tolerate transient errors anyway, that is not a blocker.

Tao


  reply	other threads:[~2026-09-01 13:09 UTC|newest]

Thread overview: 54+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <20260518174013.4b72dd50a5bcb89daaed1f62@linux-foundation.org>
     [not found] ` <d43125ff-cc66-49b7-b16d-1b2650c68c23@I-love.SAKURA.ne.jp>
     [not found]   ` <ag0lS_CbKO9R5CV8@fedora>
     [not found]     ` <94076bc9-2c09-4bb6-8468-b6b8af419cb9@I-love.SAKURA.ne.jp>
     [not found]       ` <ag1nfIFcykmQHbkk@fedora>
     [not found]         ` <1ab8c579-eb76-4227-8a72-6ec819135219@I-love.SAKURA.ne.jp>
     [not found]           ` <ag1223nAa0wZ8ALC@fedora>
     [not found]             ` <fda8abc8-6aa2-463b-bf72-865f6b838034@I-love.SAKURA.ne.jp>
     [not found]               ` <ahRocb0Vs_m6RF_O@fedora>
     [not found]                 ` <1a9f53d4-6f48-4df8-a3d8-2b0e442a163a@I-love.SAKURA.ne.jp>
2026-05-27  1:20                   ` [PATCH v3] loop: Fix NULL pointer dereference in lo_rw_aio() Ming Lei
2026-05-27  1:35                     ` Tetsuo Handa
2026-05-27  3:00                       ` Ming Lei
2026-05-27 11:29                         ` Tetsuo Handa
2026-05-27 18:11                           ` Damien Le Moal
2026-05-28  8:38                             ` Christoph Hellwig
2026-05-28 10:16                               ` Qu Wenruo
2026-06-01 14:40                                 ` Christoph Hellwig
2026-06-01 16:29                                   ` Brian Foster
2026-06-01 22:27                                     ` Qu Wenruo
2026-06-01 15:29                                 ` Ming Lei
2026-06-01 21:51                                   ` Hillf Danton
2026-06-01 22:14                                     ` Ming Lei
2026-06-01 23:17                                       ` Hillf Danton
2026-06-01 23:36                                         ` Ming Lei
2026-06-02  2:02                                           ` Hillf Danton
2026-05-28  5:43                         ` Hillf Danton
2026-05-28 23:00                           ` Hillf Danton
2026-05-29  0:14                             ` Tetsuo Handa
2026-05-29  7:04                               ` Hillf Danton
2026-05-29 22:05                                 ` Hillf Danton
2026-05-30 23:57                                   ` Tetsuo Handa
2026-06-07 10:54                                     ` [PATCH v4] " Tetsuo Handa
2026-06-09 17:50                                       ` Al Viro
2026-06-13 11:00                                         ` Tetsuo Handa
2026-06-19 14:33                                           ` Tetsuo Handa
2026-06-20  7:39                                             ` Al Viro
2026-06-20  9:42                                               ` Tetsuo Handa
2026-07-13  3:04                                         ` Hillf Danton
2026-07-13 11:02                                           ` Tetsuo Handa
2026-07-14  4:38                                             ` Hillf Danton
2026-07-16  0:05                                               ` [PATCH v5] " Tetsuo Handa
2026-08-23 11:17                                                 ` [PATCH v6] " Tetsuo Handa
2026-08-23 15:57                                                   ` Markus Elfring
2026-08-24 22:06                                                     ` Tetsuo Handa
2026-08-24 22:53                                                       ` Bart Van Assche
2026-08-24 23:24                                                         ` Bart Van Assche
2026-08-25 15:13                                                           ` Tetsuo Handa
2026-08-25 22:18                                                             ` Bart Van Assche
2026-08-25 23:28                                                               ` Tetsuo Handa
2026-08-25 23:16                                                             ` Bart Van Assche
2026-08-26 10:37                                                               ` Tetsuo Handa
2026-08-26 17:44                                                                 ` Bart Van Assche
2026-08-27 15:30                                                                   ` Tetsuo Handa
2026-08-27 17:28                                                                     ` Bart Van Assche
2026-08-28 15:53                                                                       ` [PATCH v7] " Tetsuo Handa
2026-08-28 16:29                                                                         ` Bart Van Assche
2026-08-29  5:17                                                                           ` Tetsuo Handa
2026-08-29  5:55                                                                             ` Tetsuo Handa
2026-08-31 14:11                                                                               ` Tao Cui
2026-08-31 15:49                                                                                 ` Tetsuo Handa
2026-09-01 13:08                                                                                   ` Tao Cui [this message]
2026-08-31 16:11                                                                                 ` Bart Van Assche
2026-08-31 16:14                                                                             ` Bart Van Assche

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=d1209b70-7e76-4415-92c9-6ddc192a3347@linux.dev \
    --to=cui.tao@linux.dev \
    --cc=Markus.Elfring@web.de \
    --cc=axboe@kernel.dk \
    --cc=brauner@kernel.org \
    --cc=broonie@kernel.org \
    --cc=bvanassche@acm.org \
    --cc=cuitao@kylinos.cn \
    --cc=dlemoal@kernel.org \
    --cc=hch@infradead.org \
    --cc=hch@lst.de \
    --cc=hdanton@sina.com \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-next@vger.kernel.org \
    --cc=lkp@intel.com \
    --cc=oe-lkp@lists.linux.dev \
    --cc=oliver.sang@intel.com \
    --cc=penguin-kernel@I-love.SAKURA.ne.jp \
    --cc=torvalds@linux-foundation.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox