diff mbox series

[v3] x86: irq: Do not BUG_ON multiple unbind calls for shared pirqs

Message ID 1580290087-20636-1-git-send-email-vrd@amazon.de (mailing list archive)
State New, archived
Headers show
Series [v3] x86: irq: Do not BUG_ON multiple unbind calls for shared pirqs | expand

Commit Message

Varad Gautam Jan. 29, 2020, 9:28 a.m. UTC
XEN_DOMCTL_destroydomain creates a continuation if domain_kill -ERESTARTS.
In that scenario, it is possible to receive multiple _pirq_guest_unbind
calls for the same pirq from domain_kill, if the pirq has not yet been
removed from the domain's pirq_tree, as:
  domain_kill()
    -> domain_relinquish_resources()
      -> pci_release_devices()
        -> pci_clean_dpci_irq()
          -> pirq_guest_unbind()
            -> __pirq_guest_unbind()

For a shared pirq (nr_guests > 1), the first call would zap the current
domain from the pirq's guests[] list, but the action handler is never freed
as there are other guests using this pirq. As a result, on the second call,
__pirq_guest_unbind searches for the current domain which has been removed
from the guests[] list, and hits a BUG_ON.

Make __pirq_guest_unbind safe to be called multiple times by letting xen
continue if a shared pirq has already been unbound from this guest. The
PIRQ will be cleaned up from the domain's pirq_tree during the destruction
in complete_domain_destroy anyways.

Signed-off-by: Varad Gautam <vrd@amazon.de>
Reviewed-by: Paul Durrant <paul@xen.org>
CC: Jan Beulich <jbeulich@suse.com>
CC: Julien Grall <julien@xen.org>
CC: Roger Pau Monné <roger.pau@citrix.com>
CC: Andrew Cooper <andrew.cooper3@citrix.com>

v2: Split the check on action->nr_guests > 0 and make it an ASSERT.
v3: Style fixups.
---
 xen/arch/x86/irq.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

Comments

Julien Grall Jan. 29, 2020, 10:08 a.m. UTC | #1
Hi,

On 29/01/2020 09:28, Varad Gautam wrote:
> XEN_DOMCTL_destroydomain creates a continuation if domain_kill -ERESTARTS.
> In that scenario, it is possible to receive multiple _pirq_guest_unbind
> calls for the same pirq from domain_kill, if the pirq has not yet been
> removed from the domain's pirq_tree, as:
>    domain_kill()
>      -> domain_relinquish_resources()
>        -> pci_release_devices()
>          -> pci_clean_dpci_irq()
>            -> pirq_guest_unbind()
>              -> __pirq_guest_unbind()
> 
> For a shared pirq (nr_guests > 1), the first call would zap the current
> domain from the pirq's guests[] list, but the action handler is never freed
> as there are other guests using this pirq. As a result, on the second call,
> __pirq_guest_unbind searches for the current domain which has been removed
> from the guests[] list, and hits a BUG_ON.
> 
> Make __pirq_guest_unbind safe to be called multiple times by letting xen
> continue if a shared pirq has already been unbound from this guest. The
> PIRQ will be cleaned up from the domain's pirq_tree during the destruction
> in complete_domain_destroy anyways.
> 
> Signed-off-by: Varad Gautam <vrd@amazon.de>
> Reviewed-by: Paul Durrant <paul@xen.org>
> CC: Jan Beulich <jbeulich@suse.com>
> CC: Julien Grall <julien@xen.org>
> CC: Roger Pau Monné <roger.pau@citrix.com>
> CC: Andrew Cooper <andrew.cooper3@citrix.com>
> 
> v2: Split the check on action->nr_guests > 0 and make it an ASSERT.
> v3: Style fixups.
> ---
>   xen/arch/x86/irq.c | 15 ++++++++++++++-
>   1 file changed, 14 insertions(+), 1 deletion(-)
> 
> diff --git a/xen/arch/x86/irq.c b/xen/arch/x86/irq.c
> index 310ac00..4b172eb 100644
> --- a/xen/arch/x86/irq.c
> +++ b/xen/arch/x86/irq.c
> @@ -1681,7 +1681,20 @@ static irq_guest_action_t *__pirq_guest_unbind(
>   
>       for ( i = 0; (i < action->nr_guests) && (action->guest[i] != d); i++ )
>           continue;
> -    BUG_ON(i == action->nr_guests);
> +    if ( i == action->nr_guests )
> +    {
> +        ASSERT(action->nr_guests > 0);
> +        /*
> +         * In case the pirq was shared, unbound for this domain in an earlier
> +         * call, but still existed on the domain's pirq_tree, we still reach
> +         * here if there are any later unbind calls on the same pirq. Return
> +         * if such an unbind happens.
> +         */
> +        if ( action->shareable )
> +            return NULL;
> +        BUG();

Please see my comment on v2 about the BUG(). I am still unconvinced this 
is the right error handling here.

Cheers,
Roger Pau Monné Jan. 29, 2020, 10:30 a.m. UTC | #2
Hello,

Thanks for the patch! Next time could you please try to reply to the
previous questions before sending a new version:

https://lists.xenproject.org/archives/html/xen-devel/2019-07/msg00257.html

On Wed, Jan 29, 2020 at 10:28:07AM +0100, Varad Gautam wrote:
> XEN_DOMCTL_destroydomain creates a continuation if domain_kill -ERESTARTS.
> In that scenario, it is possible to receive multiple _pirq_guest_unbind
> calls for the same pirq from domain_kill, if the pirq has not yet been
> removed from the domain's pirq_tree, as:
>   domain_kill()
>     -> domain_relinquish_resources()
>       -> pci_release_devices()
>         -> pci_clean_dpci_irq()
>           -> pirq_guest_unbind()
>             -> __pirq_guest_unbind()
> 
> For a shared pirq (nr_guests > 1), the first call would zap the current
> domain from the pirq's guests[] list, but the action handler is never freed
> as there are other guests using this pirq. As a result, on the second call,
> __pirq_guest_unbind searches for the current domain which has been removed
> from the guests[] list, and hits a BUG_ON.
> 
> Make __pirq_guest_unbind safe to be called multiple times by letting xen
> continue if a shared pirq has already been unbound from this guest. The
> PIRQ will be cleaned up from the domain's pirq_tree during the destruction
> in complete_domain_destroy anyways.

So AFAICT this is because pt_pirq_softirq_active() returns true in
pci_clean_dpci_irq() and hence the iteration is stopped and
hvm_domain_irq(d)->dpci is not set to NULL.

Would it be possible to clean the already processed IRQs from the
domain pirq_tree?

pci_clean_dpci_irq() already seems to free part of this structure, and
would be nicer IMO if we didn't leave cleaned up stuff behind on
ERESTART.

Thanks, Roger.
Jan Beulich Jan. 29, 2020, 11:47 a.m. UTC | #3
On 29.01.2020 11:30, Roger Pau Monné wrote:
> Hello,
> 
> Thanks for the patch! Next time could you please try to reply to the
> previous questions before sending a new version:
> 
> https://lists.xenproject.org/archives/html/xen-devel/2019-07/msg00257.html
> 
> On Wed, Jan 29, 2020 at 10:28:07AM +0100, Varad Gautam wrote:
>> XEN_DOMCTL_destroydomain creates a continuation if domain_kill -ERESTARTS.
>> In that scenario, it is possible to receive multiple _pirq_guest_unbind
>> calls for the same pirq from domain_kill, if the pirq has not yet been
>> removed from the domain's pirq_tree, as:
>>   domain_kill()
>>     -> domain_relinquish_resources()
>>       -> pci_release_devices()
>>         -> pci_clean_dpci_irq()
>>           -> pirq_guest_unbind()
>>             -> __pirq_guest_unbind()
>>
>> For a shared pirq (nr_guests > 1), the first call would zap the current
>> domain from the pirq's guests[] list, but the action handler is never freed
>> as there are other guests using this pirq. As a result, on the second call,
>> __pirq_guest_unbind searches for the current domain which has been removed
>> from the guests[] list, and hits a BUG_ON.
>>
>> Make __pirq_guest_unbind safe to be called multiple times by letting xen
>> continue if a shared pirq has already been unbound from this guest. The
>> PIRQ will be cleaned up from the domain's pirq_tree during the destruction
>> in complete_domain_destroy anyways.
> 
> So AFAICT this is because pt_pirq_softirq_active() returns true in
> pci_clean_dpci_irq() and hence the iteration is stopped and
> hvm_domain_irq(d)->dpci is not set to NULL.
> 
> Would it be possible to clean the already processed IRQs from the
> domain pirq_tree?

This might work, perhaps by way of invoking unmap_domain_pirq()
right after pirq_guest_unbind(), as long as hvm_dirq_assist() (as
called from dpci_softirq()) can be made skip all actual work it
means to do in such a case. Unfortunately the two ->masked fields
acted upon are different between __pirq_guest_unbind() and
hvm_dirq_assist().

Jan
Jan Beulich March 5, 2020, 9:36 a.m. UTC | #4
On 29.01.2020 12:47, Jan Beulich wrote:
> On 29.01.2020 11:30, Roger Pau Monné wrote:
>> Hello,
>>
>> Thanks for the patch! Next time could you please try to reply to the
>> previous questions before sending a new version:
>>
>> https://lists.xenproject.org/archives/html/xen-devel/2019-07/msg00257.html
>>
>> On Wed, Jan 29, 2020 at 10:28:07AM +0100, Varad Gautam wrote:
>>> XEN_DOMCTL_destroydomain creates a continuation if domain_kill -ERESTARTS.
>>> In that scenario, it is possible to receive multiple _pirq_guest_unbind
>>> calls for the same pirq from domain_kill, if the pirq has not yet been
>>> removed from the domain's pirq_tree, as:
>>>   domain_kill()
>>>     -> domain_relinquish_resources()
>>>       -> pci_release_devices()
>>>         -> pci_clean_dpci_irq()
>>>           -> pirq_guest_unbind()
>>>             -> __pirq_guest_unbind()
>>>
>>> For a shared pirq (nr_guests > 1), the first call would zap the current
>>> domain from the pirq's guests[] list, but the action handler is never freed
>>> as there are other guests using this pirq. As a result, on the second call,
>>> __pirq_guest_unbind searches for the current domain which has been removed
>>> from the guests[] list, and hits a BUG_ON.
>>>
>>> Make __pirq_guest_unbind safe to be called multiple times by letting xen
>>> continue if a shared pirq has already been unbound from this guest. The
>>> PIRQ will be cleaned up from the domain's pirq_tree during the destruction
>>> in complete_domain_destroy anyways.
>>
>> So AFAICT this is because pt_pirq_softirq_active() returns true in
>> pci_clean_dpci_irq() and hence the iteration is stopped and
>> hvm_domain_irq(d)->dpci is not set to NULL.
>>
>> Would it be possible to clean the already processed IRQs from the
>> domain pirq_tree?
> 
> This might work, perhaps by way of invoking unmap_domain_pirq()
> right after pirq_guest_unbind(), as long as hvm_dirq_assist() (as
> called from dpci_softirq()) can be made skip all actual work it
> means to do in such a case. Unfortunately the two ->masked fields
> acted upon are different between __pirq_guest_unbind() and
> hvm_dirq_assist().

Ping? Unless I hear back soon, I'm afraid I'm going to drop this
patch from my "pending" mail folder, as not being agreed whether
to stick to the current version or whether to go this alternative
route. A more "natural" approach to fixing the issue would be
quite nice, after all.

Jan
Durrant, Paul March 5, 2020, 5:37 p.m. UTC | #5
> -----Original Message-----
> From: Xen-devel <xen-devel-bounces@lists.xenproject.org> On Behalf Of Jan Beulich
> Sent: 05 March 2020 09:37
> To: Gautam, Varad <vrd@amazon.de>
> Cc: xen-devel@lists.xenproject.org; Andrew Cooper <andrew.cooper3@citrix.com>; Julien Grall
> <julien@xen.org>; Roger Pau Monné <roger.pau@citrix.com>
> Subject: Re: [Xen-devel] [PATCH v3] x86: irq: Do not BUG_ON multiple unbind calls for shared pirqs
> 
> On 29.01.2020 12:47, Jan Beulich wrote:
> > On 29.01.2020 11:30, Roger Pau Monné wrote:
> >> Hello,
> >>
> >> Thanks for the patch! Next time could you please try to reply to the
> >> previous questions before sending a new version:
> >>
> >> https://lists.xenproject.org/archives/html/xen-devel/2019-07/msg00257.html
> >>
> >> On Wed, Jan 29, 2020 at 10:28:07AM +0100, Varad Gautam wrote:
> >>> XEN_DOMCTL_destroydomain creates a continuation if domain_kill -ERESTARTS.
> >>> In that scenario, it is possible to receive multiple _pirq_guest_unbind
> >>> calls for the same pirq from domain_kill, if the pirq has not yet been
> >>> removed from the domain's pirq_tree, as:
> >>>   domain_kill()
> >>>     -> domain_relinquish_resources()
> >>>       -> pci_release_devices()
> >>>         -> pci_clean_dpci_irq()
> >>>           -> pirq_guest_unbind()
> >>>             -> __pirq_guest_unbind()
> >>>
> >>> For a shared pirq (nr_guests > 1), the first call would zap the current
> >>> domain from the pirq's guests[] list, but the action handler is never freed
> >>> as there are other guests using this pirq. As a result, on the second call,
> >>> __pirq_guest_unbind searches for the current domain which has been removed
> >>> from the guests[] list, and hits a BUG_ON.
> >>>
> >>> Make __pirq_guest_unbind safe to be called multiple times by letting xen
> >>> continue if a shared pirq has already been unbound from this guest. The
> >>> PIRQ will be cleaned up from the domain's pirq_tree during the destruction
> >>> in complete_domain_destroy anyways.
> >>
> >> So AFAICT this is because pt_pirq_softirq_active() returns true in
> >> pci_clean_dpci_irq() and hence the iteration is stopped and
> >> hvm_domain_irq(d)->dpci is not set to NULL.
> >>
> >> Would it be possible to clean the already processed IRQs from the
> >> domain pirq_tree?
> >
> > This might work, perhaps by way of invoking unmap_domain_pirq()
> > right after pirq_guest_unbind(), as long as hvm_dirq_assist() (as
> > called from dpci_softirq()) can be made skip all actual work it
> > means to do in such a case. Unfortunately the two ->masked fields
> > acted upon are different between __pirq_guest_unbind() and
> > hvm_dirq_assist().
> 
> Ping? Unless I hear back soon, I'm afraid I'm going to drop this
> patch from my "pending" mail folder, as not being agreed whether
> to stick to the current version or whether to go this alternative
> route. A more "natural" approach to fixing the issue would be
> quite nice, after all.

I'll try to pick this up tomorrow as I helped diagnose the problem being fixed.

  Paul

> 
> Jan
> 
> _______________________________________________
> Xen-devel mailing list
> Xen-devel@lists.xenproject.org
> https://lists.xenproject.org/mailman/listinfo/xen-devel
Durrant, Paul March 6, 2020, 3:07 p.m. UTC | #6
> -----Original Message-----
> From: Xen-devel <xen-devel-bounces@lists.xenproject.org> On Behalf Of Durrant, Paul
> Sent: 05 March 2020 17:37
> To: Jan Beulich <jbeulich@suse.com>; Gautam, Varad <vrd@amazon.de>
> Cc: xen-devel@lists.xenproject.org; Roger Pau Monné <roger.pau@citrix.com>; Julien Grall
> <julien@xen.org>; Andrew Cooper <andrew.cooper3@citrix.com>
> Subject: Re: [Xen-devel] [PATCH v3] x86: irq: Do not BUG_ON multiple unbind calls for shared pirqs
> 
> > -----Original Message-----
> > From: Xen-devel <xen-devel-bounces@lists.xenproject.org> On Behalf Of Jan Beulich
> > Sent: 05 March 2020 09:37
> > To: Gautam, Varad <vrd@amazon.de>
> > Cc: xen-devel@lists.xenproject.org; Andrew Cooper <andrew.cooper3@citrix.com>; Julien Grall
> > <julien@xen.org>; Roger Pau Monné <roger.pau@citrix.com>
> > Subject: Re: [Xen-devel] [PATCH v3] x86: irq: Do not BUG_ON multiple unbind calls for shared pirqs
> >
> > On 29.01.2020 12:47, Jan Beulich wrote:
> > > On 29.01.2020 11:30, Roger Pau Monné wrote:
> > >> Hello,
> > >>
> > >> Thanks for the patch! Next time could you please try to reply to the
> > >> previous questions before sending a new version:
> > >>
> > >> https://lists.xenproject.org/archives/html/xen-devel/2019-07/msg00257.html
> > >>
> > >> On Wed, Jan 29, 2020 at 10:28:07AM +0100, Varad Gautam wrote:
> > >>> XEN_DOMCTL_destroydomain creates a continuation if domain_kill -ERESTARTS.
> > >>> In that scenario, it is possible to receive multiple _pirq_guest_unbind
> > >>> calls for the same pirq from domain_kill, if the pirq has not yet been
> > >>> removed from the domain's pirq_tree, as:
> > >>>   domain_kill()
> > >>>     -> domain_relinquish_resources()
> > >>>       -> pci_release_devices()
> > >>>         -> pci_clean_dpci_irq()
> > >>>           -> pirq_guest_unbind()
> > >>>             -> __pirq_guest_unbind()
> > >>>
> > >>> For a shared pirq (nr_guests > 1), the first call would zap the current
> > >>> domain from the pirq's guests[] list, but the action handler is never freed
> > >>> as there are other guests using this pirq. As a result, on the second call,
> > >>> __pirq_guest_unbind searches for the current domain which has been removed
> > >>> from the guests[] list, and hits a BUG_ON.
> > >>>
> > >>> Make __pirq_guest_unbind safe to be called multiple times by letting xen
> > >>> continue if a shared pirq has already been unbound from this guest. The
> > >>> PIRQ will be cleaned up from the domain's pirq_tree during the destruction
> > >>> in complete_domain_destroy anyways.
> > >>
> > >> So AFAICT this is because pt_pirq_softirq_active() returns true in
> > >> pci_clean_dpci_irq() and hence the iteration is stopped and
> > >> hvm_domain_irq(d)->dpci is not set to NULL.
> > >>
> > >> Would it be possible to clean the already processed IRQs from the
> > >> domain pirq_tree?
> > >
> > > This might work, perhaps by way of invoking unmap_domain_pirq()
> > > right after pirq_guest_unbind(), as long as hvm_dirq_assist() (as
> > > called from dpci_softirq()) can be made skip all actual work it
> > > means to do in such a case. Unfortunately the two ->masked fields
> > > acted upon are different between __pirq_guest_unbind() and
> > > hvm_dirq_assist().
> >
> > Ping? Unless I hear back soon, I'm afraid I'm going to drop this
> > patch from my "pending" mail folder, as not being agreed whether
> > to stick to the current version or whether to go this alternative
> > route. A more "natural" approach to fixing the issue would be
> > quite nice, after all.
> 
> I'll try to pick this up tomorrow as I helped diagnose the problem being fixed.
> 

I'm looking at this now and I am finding the code very confusing, but I think we could achieve the cleanup by passing back the irq index out of __pirq_guest_unbind() such that pirq_guest_unbind() can call clean_domain_irq_pirq(). I still haven't got much of a clue as to how all the data structures hang together though.

  Paul
diff mbox series

Patch

diff --git a/xen/arch/x86/irq.c b/xen/arch/x86/irq.c
index 310ac00..4b172eb 100644
--- a/xen/arch/x86/irq.c
+++ b/xen/arch/x86/irq.c
@@ -1681,7 +1681,20 @@  static irq_guest_action_t *__pirq_guest_unbind(
 
     for ( i = 0; (i < action->nr_guests) && (action->guest[i] != d); i++ )
         continue;
-    BUG_ON(i == action->nr_guests);
+    if ( i == action->nr_guests )
+    {
+        ASSERT(action->nr_guests > 0);
+        /*
+         * In case the pirq was shared, unbound for this domain in an earlier
+         * call, but still existed on the domain's pirq_tree, we still reach
+         * here if there are any later unbind calls on the same pirq. Return
+         * if such an unbind happens.
+         */
+        if ( action->shareable )
+            return NULL;
+        BUG();
+    }
+
     memmove(&action->guest[i], &action->guest[i+1],
             (action->nr_guests-i-1) * sizeof(action->guest[0]));
     action->nr_guests--;