[PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()

Thomas Huth posted 1 patch 4 days, 2 hours ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260721070216.82984-1-thuth@redhat.com
Maintainers: John Snow <jsnow@redhat.com>
hw/ide/core.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
[PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Thomas Huth 4 days, 2 hours ago
From: Thomas Huth <thuth@redhat.com>

ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
two IDE drives on a bus (primary or secondary drive) to cancel all
pending DMA transfers on the drive. The code then checks
s->bus->dma->aiocb to see whether there is any IO in flight on the
*bus* and then calls blk_drain(s->blk) to wait for its completion.
However, s->bus->dma->aiocb might belong to the other drive on the
bus, and if there is no disk attached to the current drive, s->blk
is NULL. Since blk_drain() does not check its parameter for a NULL
pointer, QEMU can crash in such a case.

To fix the problem, we have to check that "blk" is not NULL before
calling blk_drain(). And we have to call blk_drain() for both drives,
otherwise the assert(s->bus->dma->aiocb == NULL) statement after
the blk_drain() might trigger if the IO in flight belongs to the
the other drive.

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/905
Reported-by: Alexander Bulekov <alxndr@bu.edu>
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4052
Reported-by: dong ling
Signed-off-by: Thomas Huth <thuth@redhat.com>
---
 v2: Drain both drives

 hw/ide/core.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/hw/ide/core.c b/hw/ide/core.c
index f78b00220b8..f43ab95b07b 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
      * In the future we'll be able to safely cancel the I/O if the
      * whole DMA operation will be submitted to disk with a single
      * aio operation with preadv/pwritev.
+     *
+     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
+     * so we have to drain both drives to get it cleared.
      */
     if (s->bus->dma->aiocb) {
         trace_ide_cancel_dma_sync_remaining();
-        blk_drain(s->blk);
+        for (int i = 0; i < 2; i++) {
+            if (s->bus->ifs[i].blk) {
+                blk_drain(s->bus->ifs[i].blk);
+            }
+        }
         assert(s->bus->dma->aiocb == NULL);
     }
 }
-- 
2.55.0
Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Mark Cave-Ayland 4 days, 1 hour ago
On 21/07/2026 08:02, Thomas Huth wrote:

> From: Thomas Huth <thuth@redhat.com>
> 
> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
> two IDE drives on a bus (primary or secondary drive) to cancel all
> pending DMA transfers on the drive. The code then checks
> s->bus->dma->aiocb to see whether there is any IO in flight on the
> *bus* and then calls blk_drain(s->blk) to wait for its completion.
> However, s->bus->dma->aiocb might belong to the other drive on the
> bus, and if there is no disk attached to the current drive, s->blk
> is NULL. Since blk_drain() does not check its parameter for a NULL
> pointer, QEMU can crash in such a case.
> 
> To fix the problem, we have to check that "blk" is not NULL before
> calling blk_drain(). And we have to call blk_drain() for both drives,
> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
> the blk_drain() might trigger if the IO in flight belongs to the
> the other drive.
> 
> Resolves: https://urldefense.proofpoint.com/v2/url?u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
> Reported-by: Alexander Bulekov <alxndr@bu.edu>
> Resolves: https://urldefense.proofpoint.com/v2/url?u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
> Reported-by: dong ling
> Signed-off-by: Thomas Huth <thuth@redhat.com>
> ---
>   v2: Drain both drives
> 
>   hw/ide/core.c | 9 ++++++++-
>   1 file changed, 8 insertions(+), 1 deletion(-)
> 
> diff --git a/hw/ide/core.c b/hw/ide/core.c
> index f78b00220b8..f43ab95b07b 100644
> --- a/hw/ide/core.c
> +++ b/hw/ide/core.c
> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>        * In the future we'll be able to safely cancel the I/O if the
>        * whole DMA operation will be submitted to disk with a single
>        * aio operation with preadv/pwritev.
> +     *
> +     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
> +     * so we have to drain both drives to get it cleared.
>        */
>       if (s->bus->dma->aiocb) {
>           trace_ide_cancel_dma_sync_remaining();
> -        blk_drain(s->blk);
> +        for (int i = 0; i < 2; i++) {
> +            if (s->bus->ifs[i].blk) {
> +                blk_drain(s->bus->ifs[i].blk);
> +            }
> +        }
>           assert(s->bus->dma->aiocb == NULL);
>       }
>   }

Doesn't ide_bus_active_if() do the right thing here for in-flight 
requests? I'd expect to be able to do something like:

   if (s->bus->dma->aiocb) {
       trace_ide_cancel_dma_sync_remaining();
       blk_drain(ide_bus_active_if(s->bus)->blk);
   }


ATB,

Mark.
Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Philippe Mathieu-Daudé 3 days, 23 hours ago
On 21/7/26 10:29, Mark Cave-Ayland wrote:
> On 21/07/2026 08:02, Thomas Huth wrote:
> 
>> From: Thomas Huth <thuth@redhat.com>
>>
>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>> two IDE drives on a bus (primary or secondary drive) to cancel all
>> pending DMA transfers on the drive. The code then checks
>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>> However, s->bus->dma->aiocb might belong to the other drive on the
>> bus, and if there is no disk attached to the current drive, s->blk
>> is NULL. Since blk_drain() does not check its parameter for a NULL
>> pointer, QEMU can crash in such a case.
>>
>> To fix the problem, we have to check that "blk" is not NULL before
>> calling blk_drain(). And we have to call blk_drain() for both drives,
>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>> the blk_drain() might trigger if the IO in flight belongs to the
>> the other drive.
>>
>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>> Reported-by: dong ling
>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>> ---
>>   v2: Drain both drives
>>
>>   hw/ide/core.c | 9 ++++++++-
>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>
>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>> index f78b00220b8..f43ab95b07b 100644
>> --- a/hw/ide/core.c
>> +++ b/hw/ide/core.c
>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>        * In the future we'll be able to safely cancel the I/O if the
>>        * whole DMA operation will be submitted to disk with a single
>>        * aio operation with preadv/pwritev.
>> +     *
>> +     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
>> +     * so we have to drain both drives to get it cleared.
>>        */
>>       if (s->bus->dma->aiocb) {
>>           trace_ide_cancel_dma_sync_remaining();
>> -        blk_drain(s->blk);
>> +        for (int i = 0; i < 2; i++) {
>> +            if (s->bus->ifs[i].blk) {
>> +                blk_drain(s->bus->ifs[i].blk);
>> +            }
>> +        }
>>           assert(s->bus->dma->aiocb == NULL);
>>       }
>>   }
> 
> Doesn't ide_bus_active_if() do the right thing here for in-flight 
> requests?

bmdma_cmd_writeb() already calls ide_bus_active_if().

Is the issue in the caller, bmdma_cmd_writeb(), instead of
here in ide_cancel_dma_sync()?

> I'd expect to be able to do something like:
> 
>    if (s->bus->dma->aiocb) {
>        trace_ide_cancel_dma_sync_remaining();
>        blk_drain(ide_bus_active_if(s->bus)->blk);
>    }
> 
> 
> ATB,
> 
> Mark.
> 
> 


Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Philippe Mathieu-Daudé 3 days, 23 hours ago
On 21/7/26 11:51, Philippe Mathieu-Daudé wrote:
> On 21/7/26 10:29, Mark Cave-Ayland wrote:
>> On 21/07/2026 08:02, Thomas Huth wrote:
>>
>>> From: Thomas Huth <thuth@redhat.com>
>>>
>>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>>> two IDE drives on a bus (primary or secondary drive) to cancel all
>>> pending DMA transfers on the drive. The code then checks
>>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>>> However, s->bus->dma->aiocb might belong to the other drive on the
>>> bus, and if there is no disk attached to the current drive, s->blk
>>> is NULL. Since blk_drain() does not check its parameter for a NULL
>>> pointer, QEMU can crash in such a case.
>>>
>>> To fix the problem, we have to check that "blk" is not NULL before
>>> calling blk_drain(). And we have to call blk_drain() for both drives,
>>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>>> the blk_drain() might trigger if the IO in flight belongs to the
>>> the other drive.
>>>
>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>>> Reported-by: dong ling
>>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>>> ---
>>>   v2: Drain both drives
>>>
>>>   hw/ide/core.c | 9 ++++++++-
>>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>>> index f78b00220b8..f43ab95b07b 100644
>>> --- a/hw/ide/core.c
>>> +++ b/hw/ide/core.c
>>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>>        * In the future we'll be able to safely cancel the I/O if the
>>>        * whole DMA operation will be submitted to disk with a single
>>>        * aio operation with preadv/pwritev.
>>> +     *
>>> +     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
>>> +     * so we have to drain both drives to get it cleared.
>>>        */
>>>       if (s->bus->dma->aiocb) {
>>>           trace_ide_cancel_dma_sync_remaining();
>>> -        blk_drain(s->blk);
>>> +        for (int i = 0; i < 2; i++) {
>>> +            if (s->bus->ifs[i].blk) {
>>> +                blk_drain(s->bus->ifs[i].blk);
>>> +            }
>>> +        }
>>>           assert(s->bus->dma->aiocb == NULL);
>>>       }
>>>   }
>>
>> Doesn't ide_bus_active_if() do the right thing here for in-flight 
>> requests?
> 
> bmdma_cmd_writeb() already calls ide_bus_active_if().
> 
> Is the issue in the caller, bmdma_cmd_writeb(), instead of
> here in ide_cancel_dma_sync()?

i.e.:

-- >8 --
diff --git a/hw/ide/pci.c b/hw/ide/pci.c
index 7ce1ae67ab2..e7c6af9a49e 100644
--- a/hw/ide/pci.c
+++ b/hw/ide/pci.c
@@ -384,7 +384,7 @@ void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val)
      /* Ignore writes to SSBM if it keeps the old value */
      if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
          if (!(val & BM_CMD_START)) {
-            ide_cancel_dma_sync(ide_bus_active_if(bm->bus));
+            ide_cancel_dma_sync(bmdma_active_if(bm));
              bm->status &= ~BM_STATUS_DMAING;
          } else {
              bm->cur_addr = bm->addr;
---

> 
>> I'd expect to be able to do something like:
>>
>>    if (s->bus->dma->aiocb) {
>>        trace_ide_cancel_dma_sync_remaining();
>>        blk_drain(ide_bus_active_if(s->bus)->blk);
>>    }
>>
>>
>> ATB,
>>
>> Mark.
>>
>>
> 
> 


Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Thomas Huth 3 days, 23 hours ago
On 21/07/2026 11.58, Philippe Mathieu-Daudé wrote:
> On 21/7/26 11:51, Philippe Mathieu-Daudé wrote:
>> On 21/7/26 10:29, Mark Cave-Ayland wrote:
>>> On 21/07/2026 08:02, Thomas Huth wrote:
>>>
>>>> From: Thomas Huth <thuth@redhat.com>
>>>>
>>>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>>>> two IDE drives on a bus (primary or secondary drive) to cancel all
>>>> pending DMA transfers on the drive. The code then checks
>>>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>>>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>>>> However, s->bus->dma->aiocb might belong to the other drive on the
>>>> bus, and if there is no disk attached to the current drive, s->blk
>>>> is NULL. Since blk_drain() does not check its parameter for a NULL
>>>> pointer, QEMU can crash in such a case.
>>>>
>>>> To fix the problem, we have to check that "blk" is not NULL before
>>>> calling blk_drain(). And we have to call blk_drain() for both drives,
>>>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>>>> the blk_drain() might trigger if the IO in flight belongs to the
>>>> the other drive.
>>>>
>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>>>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>>>> Reported-by: dong ling
>>>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>>>> ---
>>>>   v2: Drain both drives
>>>>
>>>>   hw/ide/core.c | 9 ++++++++-
>>>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>>>> index f78b00220b8..f43ab95b07b 100644
>>>> --- a/hw/ide/core.c
>>>> +++ b/hw/ide/core.c
>>>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>>>        * In the future we'll be able to safely cancel the I/O if the
>>>>        * whole DMA operation will be submitted to disk with a single
>>>>        * aio operation with preadv/pwritev.
>>>> +     *
>>>> +     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
>>>> +     * so we have to drain both drives to get it cleared.
>>>>        */
>>>>       if (s->bus->dma->aiocb) {
>>>>           trace_ide_cancel_dma_sync_remaining();
>>>> -        blk_drain(s->blk);
>>>> +        for (int i = 0; i < 2; i++) {
>>>> +            if (s->bus->ifs[i].blk) {
>>>> +                blk_drain(s->bus->ifs[i].blk);
>>>> +            }
>>>> +        }
>>>>           assert(s->bus->dma->aiocb == NULL);
>>>>       }
>>>>   }
>>>
>>> Doesn't ide_bus_active_if() do the right thing here for in-flight requests?
>>
>> bmdma_cmd_writeb() already calls ide_bus_active_if().
>>
>> Is the issue in the caller, bmdma_cmd_writeb(), instead of
>> here in ide_cancel_dma_sync()?
> 
> i.e.:
> 
> -- >8 --
> diff --git a/hw/ide/pci.c b/hw/ide/pci.c
> index 7ce1ae67ab2..e7c6af9a49e 100644
> --- a/hw/ide/pci.c
> +++ b/hw/ide/pci.c
> @@ -384,7 +384,7 @@ void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val)
>       /* Ignore writes to SSBM if it keeps the old value */
>       if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
>           if (!(val & BM_CMD_START)) {
> -            ide_cancel_dma_sync(ide_bus_active_if(bm->bus));
> +            ide_cancel_dma_sync(bmdma_active_if(bm));
>               bm->status &= ~BM_STATUS_DMAING;
>           } else {
>               bm->cur_addr = bm->addr;
No, I don't think this will fix all cases. While 
https://gitlab.com/qemu-project/qemu/-/work_items/905 has a call trace with 
bmdma_cmd_writeb() in it, 
https://gitlab.com/qemu-project/qemu/-/work_items/4052 is slightly 
different: The backtrace shows ide_bus_perform_srst() ->  ide_perform_srst() 
-> ide_cancel_dma_sync() here instead. I'd say let's play safe and always 
drain both interfaces. WDYT?

  Thomas


Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Philippe Mathieu-Daudé 3 days, 22 hours ago
On 21/7/26 12:21, Thomas Huth wrote:
> On 21/07/2026 11.58, Philippe Mathieu-Daudé wrote:
>> On 21/7/26 11:51, Philippe Mathieu-Daudé wrote:
>>> On 21/7/26 10:29, Mark Cave-Ayland wrote:
>>>> On 21/07/2026 08:02, Thomas Huth wrote:
>>>>
>>>>> From: Thomas Huth <thuth@redhat.com>
>>>>>
>>>>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>>>>> two IDE drives on a bus (primary or secondary drive) to cancel all
>>>>> pending DMA transfers on the drive. The code then checks
>>>>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>>>>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>>>>> However, s->bus->dma->aiocb might belong to the other drive on the
>>>>> bus, and if there is no disk attached to the current drive, s->blk
>>>>> is NULL. Since blk_drain() does not check its parameter for a NULL
>>>>> pointer, QEMU can crash in such a case.
>>>>>
>>>>> To fix the problem, we have to check that "blk" is not NULL before
>>>>> calling blk_drain(). And we have to call blk_drain() for both drives,
>>>>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>>>>> the blk_drain() might trigger if the IO in flight belongs to the
>>>>> the other drive.
>>>>>
>>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>>>>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>>>>> Reported-by: dong ling
>>>>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>>>>> ---
>>>>>   v2: Drain both drives
>>>>>
>>>>>   hw/ide/core.c | 9 ++++++++-
>>>>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>>>>
>>>>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>>>>> index f78b00220b8..f43ab95b07b 100644
>>>>> --- a/hw/ide/core.c
>>>>> +++ b/hw/ide/core.c
>>>>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>>>>        * In the future we'll be able to safely cancel the I/O if the
>>>>>        * whole DMA operation will be submitted to disk with a single
>>>>>        * aio operation with preadv/pwritev.
>>>>> +     *
>>>>> +     * Note: s->bus->dma->aiocb might belong to the adjacent 
>>>>> IDEState,
>>>>> +     * so we have to drain both drives to get it cleared.
>>>>>        */
>>>>>       if (s->bus->dma->aiocb) {
>>>>>           trace_ide_cancel_dma_sync_remaining();
>>>>> -        blk_drain(s->blk);
>>>>> +        for (int i = 0; i < 2; i++) {
>>>>> +            if (s->bus->ifs[i].blk) {
>>>>> +                blk_drain(s->bus->ifs[i].blk);
>>>>> +            }
>>>>> +        }
>>>>>           assert(s->bus->dma->aiocb == NULL);
>>>>>       }
>>>>>   }
>>>>
>>>> Doesn't ide_bus_active_if() do the right thing here for in-flight 
>>>> requests?
>>>
>>> bmdma_cmd_writeb() already calls ide_bus_active_if().
>>>
>>> Is the issue in the caller, bmdma_cmd_writeb(), instead of
>>> here in ide_cancel_dma_sync()?
>>
>> i.e.:
>>
>> -- >8 --
>> diff --git a/hw/ide/pci.c b/hw/ide/pci.c
>> index 7ce1ae67ab2..e7c6af9a49e 100644
>> --- a/hw/ide/pci.c
>> +++ b/hw/ide/pci.c
>> @@ -384,7 +384,7 @@ void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val)
>>       /* Ignore writes to SSBM if it keeps the old value */
>>       if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
>>           if (!(val & BM_CMD_START)) {
>> -            ide_cancel_dma_sync(ide_bus_active_if(bm->bus));
>> +            ide_cancel_dma_sync(bmdma_active_if(bm));
>>               bm->status &= ~BM_STATUS_DMAING;
>>           } else {
>>               bm->cur_addr = bm->addr;
> No, I don't think this will fix all cases. While https://gitlab.com/ 
> qemu-project/qemu/-/work_items/905 has a call trace with 
> bmdma_cmd_writeb() in it, https://gitlab.com/qemu-project/qemu/-/ 
> work_items/4052 is slightly different: The backtrace shows 
> ide_bus_perform_srst() ->  ide_perform_srst() -> ide_cancel_dma_sync() 
> here instead. I'd say let's play safe and always drain both interfaces. 
> WDYT?

I dunno I'm kind of lost. Can't blk_bs() handle NULL blk?

-- >8 --
diff --git a/block/block-backend.c b/block/block-backend.c
index 37ba7e9fc40..b5d84c9789a 100644
--- a/block/block-backend.c
+++ b/block/block-backend.c
@@ -760,7 +760,7 @@ BlockBackend *blk_by_name(const char *name)
  BlockDriverState *blk_bs(BlockBackend *blk)
  {
      IO_CODE();
-    return blk->root ? blk->root->bs : NULL;
+    return (blk && blk->root) ? blk->root->bs : NULL;
  }
---

Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Thomas Huth 3 days, 22 hours ago
On 21/07/2026 12.55, Philippe Mathieu-Daudé wrote:
> On 21/7/26 12:21, Thomas Huth wrote:
>> On 21/07/2026 11.58, Philippe Mathieu-Daudé wrote:
>>> On 21/7/26 11:51, Philippe Mathieu-Daudé wrote:
>>>> On 21/7/26 10:29, Mark Cave-Ayland wrote:
>>>>> On 21/07/2026 08:02, Thomas Huth wrote:
>>>>>
>>>>>> From: Thomas Huth <thuth@redhat.com>
>>>>>>
>>>>>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>>>>>> two IDE drives on a bus (primary or secondary drive) to cancel all
>>>>>> pending DMA transfers on the drive. The code then checks
>>>>>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>>>>>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>>>>>> However, s->bus->dma->aiocb might belong to the other drive on the
>>>>>> bus, and if there is no disk attached to the current drive, s->blk
>>>>>> is NULL. Since blk_drain() does not check its parameter for a NULL
>>>>>> pointer, QEMU can crash in such a case.
>>>>>>
>>>>>> To fix the problem, we have to check that "blk" is not NULL before
>>>>>> calling blk_drain(). And we have to call blk_drain() for both drives,
>>>>>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>>>>>> the blk_drain() might trigger if the IO in flight belongs to the
>>>>>> the other drive.
>>>>>>
>>>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>>>>>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>>>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>>>>>> Reported-by: dong ling
>>>>>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>>>>>> ---
>>>>>>   v2: Drain both drives
>>>>>>
>>>>>>   hw/ide/core.c | 9 ++++++++-
>>>>>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>>>>>
>>>>>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>>>>>> index f78b00220b8..f43ab95b07b 100644
>>>>>> --- a/hw/ide/core.c
>>>>>> +++ b/hw/ide/core.c
>>>>>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>>>>>        * In the future we'll be able to safely cancel the I/O if the
>>>>>>        * whole DMA operation will be submitted to disk with a single
>>>>>>        * aio operation with preadv/pwritev.
>>>>>> +     *
>>>>>> +     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
>>>>>> +     * so we have to drain both drives to get it cleared.
>>>>>>        */
>>>>>>       if (s->bus->dma->aiocb) {
>>>>>>           trace_ide_cancel_dma_sync_remaining();
>>>>>> -        blk_drain(s->blk);
>>>>>> +        for (int i = 0; i < 2; i++) {
>>>>>> +            if (s->bus->ifs[i].blk) {
>>>>>> +                blk_drain(s->bus->ifs[i].blk);
>>>>>> +            }
>>>>>> +        }
>>>>>>           assert(s->bus->dma->aiocb == NULL);
>>>>>>       }
>>>>>>   }
>>>>>
>>>>> Doesn't ide_bus_active_if() do the right thing here for in-flight 
>>>>> requests?
>>>>
>>>> bmdma_cmd_writeb() already calls ide_bus_active_if().
>>>>
>>>> Is the issue in the caller, bmdma_cmd_writeb(), instead of
>>>> here in ide_cancel_dma_sync()?
>>>
>>> i.e.:
>>>
>>> -- >8 --
>>> diff --git a/hw/ide/pci.c b/hw/ide/pci.c
>>> index 7ce1ae67ab2..e7c6af9a49e 100644
>>> --- a/hw/ide/pci.c
>>> +++ b/hw/ide/pci.c
>>> @@ -384,7 +384,7 @@ void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val)
>>>       /* Ignore writes to SSBM if it keeps the old value */
>>>       if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
>>>           if (!(val & BM_CMD_START)) {
>>> -            ide_cancel_dma_sync(ide_bus_active_if(bm->bus));
>>> +            ide_cancel_dma_sync(bmdma_active_if(bm));
>>>               bm->status &= ~BM_STATUS_DMAING;
>>>           } else {
>>>               bm->cur_addr = bm->addr;
>> No, I don't think this will fix all cases. While https://gitlab.com/ qemu- 
>> project/qemu/-/work_items/905 has a call trace with bmdma_cmd_writeb() in 
>> it, https://gitlab.com/qemu-project/qemu/-/ work_items/4052 is slightly 
>> different: The backtrace shows ide_bus_perform_srst() ->  
>> ide_perform_srst() -> ide_cancel_dma_sync() here instead. I'd say let's 
>> play safe and always drain both interfaces. WDYT?
> 
> I dunno I'm kind of lost. Can't blk_bs() handle NULL blk?
> 
> -- >8 --
> diff --git a/block/block-backend.c b/block/block-backend.c
> index 37ba7e9fc40..b5d84c9789a 100644
> --- a/block/block-backend.c
> +++ b/block/block-backend.c
> @@ -760,7 +760,7 @@ BlockBackend *blk_by_name(const char *name)
>   BlockDriverState *blk_bs(BlockBackend *blk)
>   {
>       IO_CODE();
> -    return blk->root ? blk->root->bs : NULL;
> +    return (blk && blk->root) ? blk->root->bs : NULL;
>   }
That would maybe help with the NULL pointer crash, but it would not prevent 
the assert(s->bus->dma->aiocb == NULL) from triggering.

Simply draining both drives seems to be the only solution that I see right 
now, unless we want to remove the assert() statement?

  Thomas


Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Philippe Mathieu-Daudé 3 days, 22 hours ago
On 21/7/26 13:08, Thomas Huth wrote:
> On 21/07/2026 12.55, Philippe Mathieu-Daudé wrote:
>> On 21/7/26 12:21, Thomas Huth wrote:
>>> On 21/07/2026 11.58, Philippe Mathieu-Daudé wrote:
>>>> On 21/7/26 11:51, Philippe Mathieu-Daudé wrote:
>>>>> On 21/7/26 10:29, Mark Cave-Ayland wrote:
>>>>>> On 21/07/2026 08:02, Thomas Huth wrote:
>>>>>>
>>>>>>> From: Thomas Huth <thuth@redhat.com>
>>>>>>>
>>>>>>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>>>>>>> two IDE drives on a bus (primary or secondary drive) to cancel all
>>>>>>> pending DMA transfers on the drive. The code then checks
>>>>>>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>>>>>>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>>>>>>> However, s->bus->dma->aiocb might belong to the other drive on the
>>>>>>> bus, and if there is no disk attached to the current drive, s->blk
>>>>>>> is NULL. Since blk_drain() does not check its parameter for a NULL
>>>>>>> pointer, QEMU can crash in such a case.
>>>>>>>
>>>>>>> To fix the problem, we have to check that "blk" is not NULL before
>>>>>>> calling blk_drain(). And we have to call blk_drain() for both 
>>>>>>> drives,
>>>>>>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>>>>>>> the blk_drain() might trigger if the IO in flight belongs to the
>>>>>>> the other drive.
>>>>>>>
>>>>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>>>>>>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>>>>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>>>>>>> Reported-by: dong ling
>>>>>>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>>>>>>> ---
>>>>>>>   v2: Drain both drives
>>>>>>>
>>>>>>>   hw/ide/core.c | 9 ++++++++-
>>>>>>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>>>>>>
>>>>>>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>>>>>>> index f78b00220b8..f43ab95b07b 100644
>>>>>>> --- a/hw/ide/core.c
>>>>>>> +++ b/hw/ide/core.c
>>>>>>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>>>>>>        * In the future we'll be able to safely cancel the I/O if the
>>>>>>>        * whole DMA operation will be submitted to disk with a single
>>>>>>>        * aio operation with preadv/pwritev.
>>>>>>> +     *
>>>>>>> +     * Note: s->bus->dma->aiocb might belong to the adjacent 
>>>>>>> IDEState,
>>>>>>> +     * so we have to drain both drives to get it cleared.
>>>>>>>        */
>>>>>>>       if (s->bus->dma->aiocb) {
>>>>>>>           trace_ide_cancel_dma_sync_remaining();
>>>>>>> -        blk_drain(s->blk);
>>>>>>> +        for (int i = 0; i < 2; i++) {
>>>>>>> +            if (s->bus->ifs[i].blk) {
>>>>>>> +                blk_drain(s->bus->ifs[i].blk);
>>>>>>> +            }
>>>>>>> +        }
>>>>>>>           assert(s->bus->dma->aiocb == NULL);
>>>>>>>       }
>>>>>>>   }
>>>>>>
>>>>>> Doesn't ide_bus_active_if() do the right thing here for in-flight 
>>>>>> requests?
>>>>>
>>>>> bmdma_cmd_writeb() already calls ide_bus_active_if().
>>>>>
>>>>> Is the issue in the caller, bmdma_cmd_writeb(), instead of
>>>>> here in ide_cancel_dma_sync()?
>>>>
>>>> i.e.:
>>>>
>>>> -- >8 --
>>>> diff --git a/hw/ide/pci.c b/hw/ide/pci.c
>>>> index 7ce1ae67ab2..e7c6af9a49e 100644
>>>> --- a/hw/ide/pci.c
>>>> +++ b/hw/ide/pci.c
>>>> @@ -384,7 +384,7 @@ void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val)
>>>>       /* Ignore writes to SSBM if it keeps the old value */
>>>>       if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
>>>>           if (!(val & BM_CMD_START)) {
>>>> -            ide_cancel_dma_sync(ide_bus_active_if(bm->bus));
>>>> +            ide_cancel_dma_sync(bmdma_active_if(bm));
>>>>               bm->status &= ~BM_STATUS_DMAING;
>>>>           } else {
>>>>               bm->cur_addr = bm->addr;
>>> No, I don't think this will fix all cases. While https://gitlab.com/ 
>>> qemu- project/qemu/-/work_items/905 has a call trace with 
>>> bmdma_cmd_writeb() in it, https://gitlab.com/qemu-project/qemu/-/ 
>>> work_items/4052 is slightly different: The backtrace shows 
>>> ide_bus_perform_srst() -> ide_perform_srst() -> ide_cancel_dma_sync() 
>>> here instead. I'd say let's play safe and always drain both 
>>> interfaces. WDYT?
>>
>> I dunno I'm kind of lost. Can't blk_bs() handle NULL blk?
>>
>> -- >8 --
>> diff --git a/block/block-backend.c b/block/block-backend.c
>> index 37ba7e9fc40..b5d84c9789a 100644
>> --- a/block/block-backend.c
>> +++ b/block/block-backend.c
>> @@ -760,7 +760,7 @@ BlockBackend *blk_by_name(const char *name)
>>   BlockDriverState *blk_bs(BlockBackend *blk)
>>   {
>>       IO_CODE();
>> -    return blk->root ? blk->root->bs : NULL;
>> +    return (blk && blk->root) ? blk->root->bs : NULL;
>>   }
> That would maybe help with the NULL pointer crash, but it would not 
> prevent the assert(s->bus->dma->aiocb == NULL) from triggering.
> 
> Simply draining both drives seems to be the only solution that I see 
> right now, unless we want to remove the assert() statement?

OK... Maybe move the comment before the for() for clarity.
Still some review from qemu-block@ team would help...

> 
>   Thomas
> 
> 


Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Philippe Mathieu-Daudé 3 days, 23 hours ago
On 21/7/26 11:58, Philippe Mathieu-Daudé wrote:
> On 21/7/26 11:51, Philippe Mathieu-Daudé wrote:
>> On 21/7/26 10:29, Mark Cave-Ayland wrote:
>>> On 21/07/2026 08:02, Thomas Huth wrote:
>>>
>>>> From: Thomas Huth <thuth@redhat.com>
>>>>
>>>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>>>> two IDE drives on a bus (primary or secondary drive) to cancel all
>>>> pending DMA transfers on the drive. The code then checks
>>>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>>>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>>>> However, s->bus->dma->aiocb might belong to the other drive on the
>>>> bus, and if there is no disk attached to the current drive, s->blk
>>>> is NULL. Since blk_drain() does not check its parameter for a NULL
>>>> pointer, QEMU can crash in such a case.
>>>>
>>>> To fix the problem, we have to check that "blk" is not NULL before
>>>> calling blk_drain(). And we have to call blk_drain() for both drives,
>>>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>>>> the blk_drain() might trigger if the IO in flight belongs to the
>>>> the other drive.
>>>>
>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>>>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>>>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>>>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>>>> Reported-by: dong ling
>>>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>>>> ---
>>>>   v2: Drain both drives
>>>>
>>>>   hw/ide/core.c | 9 ++++++++-
>>>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>>>
>>>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>>>> index f78b00220b8..f43ab95b07b 100644
>>>> --- a/hw/ide/core.c
>>>> +++ b/hw/ide/core.c
>>>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>>>        * In the future we'll be able to safely cancel the I/O if the
>>>>        * whole DMA operation will be submitted to disk with a single
>>>>        * aio operation with preadv/pwritev.
>>>> +     *
>>>> +     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
>>>> +     * so we have to drain both drives to get it cleared.
>>>>        */
>>>>       if (s->bus->dma->aiocb) {
>>>>           trace_ide_cancel_dma_sync_remaining();
>>>> -        blk_drain(s->blk);
>>>> +        for (int i = 0; i < 2; i++) {
>>>> +            if (s->bus->ifs[i].blk) {
>>>> +                blk_drain(s->bus->ifs[i].blk);
>>>> +            }
>>>> +        }
>>>>           assert(s->bus->dma->aiocb == NULL);
>>>>       }
>>>>   }
>>>
>>> Doesn't ide_bus_active_if() do the right thing here for in-flight 
>>> requests?
>>
>> bmdma_cmd_writeb() already calls ide_bus_active_if().
>>
>> Is the issue in the caller, bmdma_cmd_writeb(), instead of
>> here in ide_cancel_dma_sync()?
> 
> i.e.:
> 
> -- >8 --
> diff --git a/hw/ide/pci.c b/hw/ide/pci.c
> index 7ce1ae67ab2..e7c6af9a49e 100644
> --- a/hw/ide/pci.c
> +++ b/hw/ide/pci.c
> @@ -384,7 +384,7 @@ void bmdma_cmd_writeb(BMDMAState *bm, uint32_t val)
>       /* Ignore writes to SSBM if it keeps the old value */
>       if ((val & BM_CMD_START) != (bm->cmd & BM_CMD_START)) {
>           if (!(val & BM_CMD_START)) {

Also, shouldn't be calling ide_transfer_halt() first?

(Without IDE expert it seems late to get a fix for the release...)

> -            ide_cancel_dma_sync(ide_bus_active_if(bm->bus));
> +            ide_cancel_dma_sync(bmdma_active_if(bm));
>               bm->status &= ~BM_STATUS_DMAING;
>           } else {
>               bm->cur_addr = bm->addr;
> ---
> 
>>
>>> I'd expect to be able to do something like:
>>>
>>>    if (s->bus->dma->aiocb) {
>>>        trace_ide_cancel_dma_sync_remaining();
>>>        blk_drain(ide_bus_active_if(s->bus)->blk);
>>>    }
>>>
>>>
>>> ATB,
>>>
>>> Mark.
>>>
>>>
>>
>>
> 
> 


Re: [PATCH v2] hw/ide/core: Fix possible crash via NULL pointer in ide_cancel_dma_sync()
Posted by Thomas Huth 4 days ago
On 21/07/2026 10.29, Mark Cave-Ayland wrote:
> On 21/07/2026 08:02, Thomas Huth wrote:
> 
>> From: Thomas Huth <thuth@redhat.com>
>>
>> ide_cancel_dma_sync() is called with a "IDEState *s" for one of the
>> two IDE drives on a bus (primary or secondary drive) to cancel all
>> pending DMA transfers on the drive. The code then checks
>> s->bus->dma->aiocb to see whether there is any IO in flight on the
>> *bus* and then calls blk_drain(s->blk) to wait for its completion.
>> However, s->bus->dma->aiocb might belong to the other drive on the
>> bus, and if there is no disk attached to the current drive, s->blk
>> is NULL. Since blk_drain() does not check its parameter for a NULL
>> pointer, QEMU can crash in such a case.
>>
>> To fix the problem, we have to check that "blk" is not NULL before
>> calling blk_drain(). And we have to call blk_drain() for both drives,
>> otherwise the assert(s->bus->dma->aiocb == NULL) statement after
>> the blk_drain() might trigger if the IO in flight belongs to the
>> the other drive.
>>
>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_905&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=rCgSz13wVfk11Z-Uw5WTQViAQKkSvwpQqf_dJX1tiro&e=
>> Reported-by: Alexander Bulekov <alxndr@bu.edu>
>> Resolves: https://urldefense.proofpoint.com/v2/url? 
>> u=https-3A__gitlab.com_qemu-2Dproject_qemu_-2D_work-5Fitems_4052&d=DwIDAg&c=s883GpUCOChKOHiocYtGcg&r=c23RpsaH4D2MKyD3EPJTDa0BAxz6tV8aUJqVSoytEiY&m=BbZFP82dEZMyWzN5GnJ9XaxwmyrmYGbqWbEBNSUM_afB5JRRF3InIaZAr5UpUUh2&s=B-AkqMAsQwA0LzU5ptwPJ3ZIBAA8ncf5ZbQg8GTwaME&e=
>> Reported-by: dong ling
>> Signed-off-by: Thomas Huth <thuth@redhat.com>
>> ---
>>   v2: Drain both drives
>>
>>   hw/ide/core.c | 9 ++++++++-
>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>
>> diff --git a/hw/ide/core.c b/hw/ide/core.c
>> index f78b00220b8..f43ab95b07b 100644
>> --- a/hw/ide/core.c
>> +++ b/hw/ide/core.c
>> @@ -741,10 +741,17 @@ void ide_cancel_dma_sync(IDEState *s)
>>        * In the future we'll be able to safely cancel the I/O if the
>>        * whole DMA operation will be submitted to disk with a single
>>        * aio operation with preadv/pwritev.
>> +     *
>> +     * Note: s->bus->dma->aiocb might belong to the adjacent IDEState,
>> +     * so we have to drain both drives to get it cleared.
>>        */
>>       if (s->bus->dma->aiocb) {
>>           trace_ide_cancel_dma_sync_remaining();
>> -        blk_drain(s->blk);
>> +        for (int i = 0; i < 2; i++) {
>> +            if (s->bus->ifs[i].blk) {
>> +                blk_drain(s->bus->ifs[i].blk);
>> +            }
>> +        }
>>           assert(s->bus->dma->aiocb == NULL);
>>       }
>>   }
> 
> Doesn't ide_bus_active_if() do the right thing here for in-flight requests? 
> I'd expect to be able to do something like:
> 
>    if (s->bus->dma->aiocb) {
>        trace_ide_cancel_dma_sync_remaining();
>        blk_drain(ide_bus_active_if(s->bus)->blk);
>    }
Sounds appealing, so I gave it a try, but it does not seem to work: I can 
still trigger the problem with this code (both, the NULL pointer dereference 
and the assert() after the drain). Looks like it is possible to trigger a 
DMA transfer for the other drive, and then while the transfer is still in 
flight, switch the current drive and trigger the code path that calls 
ide_cancel_dma_sync(). So we need indeed to drain both drives, i.e. use this 
current patch here.

  Thomas