On Wed, Apr 24, 2019 at 09:12:42AM +0800, Ming Lei wrote:
> On Tue, Apr 23, 2019 at 04:07:49PM +0200, Hannes Reinecke wrote:
> > On 4/23/19 3:30 PM, Ming Lei wrote:
> > > Hi Hannes,
> > >
> > > Thanks for your response.
> > >
> > > On Tue, Apr 23, 2019 at 01:19:33PM +0200, Hannes Reinecke wrote:
> > > > On 4/22/19 5:30 AM, Ming Lei wrote:
> > [ .. ]
> > > > >
> > > > > Hi Hannes,
> > > > >
> > > > > Could you please let us know if you have better idea for this issue?
> > > > > Otherwise, I think we need to move on since it is real issue, and
> > > > > users
> > > > > want to fix that.
> > > > >
> > > > Okay. Having looked over the problem and possible alternatives, it looks
> > > > indeed like a viable solution.
> > > > I do agree that it's a sensible design to have an additional holding
> > > > area
> > > > for hardware context elements, given that they might be reassigned
> > > > during
> > > > blk_mq_realloc_hw_ctxs().
> > > >
> > > > However, I would rename the 'dead' elements to 'unused' (ie
> > > > unused_hctx_list
> > > > etc).
> > >
> > > OK, looks the name of 'unused' is better.
> > >
> > > >
> > > > And I would deallocate q->queue_hw_ctx in blk_mq_exit_queue() to make
> > > > things
> > > > more consistent.
> > >
> > > No, that is wrong.
> > >
> > > The request queue's refcount is often held when blk_cleanup_queue() is
> > > running,
> > > and blk_mq_exit_queue() is called directly by blk_cleanup_queue(). One
> > > invariant
> > > is that we have to allow most APIs running well if the request queue is
> > > live
> > > from kobject view. If q->queue_hw_ctx is freed in blk_mq_exit_queue(),
> > > it is quite easy to cause use-after-free.
> > >
> > Ah. Thought as much.
> > But then in most cases the ->queue_hw_ctx pointer is immaterial as we're
> > accessing things via the hctx pointer, which remains valid.
> >
> > > > Problem with the current patch is that in blk_mq_release() we iterate
> > > > the 'dead_hctx_list' and free up everything in there, but then blindly
> > > > call
> > > > 'kfree(q->queue_hw_ctx)' without checking if there are any context
> > > > pointers
> > > > left.
> > >
> > > If request queue is dead, it is safe to assume that there isn't any
> > > reference to request queue and q->queue_hw_ctx. Otherwise, it must be
> > > a bug somewhere.
> > >
> > Precisely.
> > What I'm trying to achieve with this is to protect against such issues,
> > which are quite easy to introduce given the complexity of the code ...
>
> But releasing q->queue_hw_ctx in blk_cleanup_queue() is easy to cause
> use-after-free even though the request queue's refcount is held. We can't
> do that simply.
>
> If someone is still trying to use q->queue_hw_ctx[] after the request
> queue is dead, the bug is in the caller of block layer API, not in
> block layer.
>
> What the patchset is trying to fix is the race in block layer, not
> users of block layer, not drivers. So far, I don't see such driver
> issue.
>
> Just thought q->queue_hw_ctx as the request queue's resource, you will
> see it is pretty reasonable to free q->queue_hw_ctx in the queue's
> release handler.
>
> >
> > > > When moving the call to blk_mq_exit_queue() we could to a simple
> > > > WARN_ON(q->queue_hw_ctx) in blk_mq_release() to ensure that everything
> > > > got
> > > > deallocated properly.
> > >
> > > At that time, hctx instance might be active, but that is fine given hctx
> > > is covered by its own kobject. What we need to do is to make sure that no
> > > any references to q->queue_hw_ctx and the request queue.
> > >
> > My point being here:
> > void blk_mq_release(struct request_queue *q)
> > {
> > struct blk_mq_hw_ctx *hctx, *next;
> >
> > cancel_delayed_work_sync(&q->requeue_work);
> >
> > /* all hctx are in .dead_hctx_list now */
> > list_for_each_entry_safe(hctx, next, &q->dead_hctx_list, hctx_list)
> > {
> > list_del_init(&hctx->hctx_list);
> > kobject_put(&hctx->kobj);
> > }
> >
> > kfree(q->queue_hw_ctx);
> >
> > /*
> > * release .mq_kobj and sw queue's kobject now because
> > * both share lifetime with request queue.
> > */
> > blk_mq_sysfs_deinit(q);
> > }
> >
> > This assumes that _all_ hctx pointers are being removed from
> > q->queue_hw_ctx, and are moved to the 'dead' list.
> > If for some reason this is not the case we'll be leaking hctx pointers here.
>
> IMO, there aren't such some reasons. When blk_mq_release() is called,
> every hctx of this request queue has been "exited" via blk_mq_exit_hctx(),
> either from blk_mq_update_nr_hw_queues() or blk_cleanup_queue().
>
> If there are hctxs not moved to the 'dead'(or 'unused') list here, it is
> simply a bug in blk-mq. However, I don't see such case now.
Or we can add the following check in blk_mq_release() for capturing
the impossible blk-mq bug:
diff --git a/block/blk-mq.c b/block/blk-mq.c
index 2ca4395f794d..f0d08087b8f6 100644
--- a/block/blk-mq.c
+++ b/block/blk-mq.c
@@ -2366,6 +2366,8 @@ blk_mq_alloc_hctx(struct request_queue *q,
hctx->queue = q;
hctx->flags = set->flags & ~BLK_MQ_F_TAG_SHARED;
+ INIT_LIST_HEAD(&hctx->hctx_list);
+
/*
* Allocate space for all possible cpus to avoid allocation at
* runtime
@@ -2680,9 +2682,14 @@ static int blk_mq_alloc_ctxs(struct request_queue *q)
void blk_mq_release(struct request_queue *q)
{
struct blk_mq_hw_ctx *hctx, *next;
+ int i;
cancel_delayed_work_sync(&q->requeue_work);
+ /* warn if live hctx is found, that shouldn't happen */
+ queue_for_each_hw_ctx(q, hctx, i)
+ WARN_ON_ONCE(hctx && list_empty(&hctx->hctx_list));
+
/* all hctx are in .dead_hctx_list now */
list_for_each_entry_safe(hctx, next, &q->dead_hctx_list, hctx_list) {
list_del_init(&hctx->hctx_list);
Thanks,
Ming