diff mbox series

[v2,1/3] memory: Track whether a Device is engaged in IO

Message ID 20220527161937.328754-2-alxndr@bu.edu (mailing list archive)
State New, archived
Headers show
Series Fix dma-reentrancy issues | expand

Commit Message

Alexander Bulekov May 27, 2022, 4:19 p.m. UTC
Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
This flag should be set/checked prior to calling a device's MemoryRegion
handlers, and set when device code initiates DMA.  The purpose of this
flag is to prevent DMA reentrancy issues. E.g.:
sdhci pio -> dma write -> sdhci mmio
nvme bh -> dma write -> nvme mmio

These issues have led to problems such as stack-exhaustion and
use-after-frees.

Assumptions:
 * Devices do not interact with their own PIO/MMIO memory-regions using
   DMA.

 * There is now way for there to be multiple simultaneous accesses to a
   device's PIO/MMIO memory-regions, or for multiple threads to perform
   DMA accesses simultaneously on behalf of a single device.

Signed-off-by: Alexander Bulekov <alxndr@bu.edu>
---
 include/hw/qdev-core.h | 3 +++
 1 file changed, 3 insertions(+)

Comments

Darren Kenny May 30, 2022, 9:58 a.m. UTC | #1
Hi Alex,

I don't know this code well enough to be certain, but is a flag
sufficient here given the intent, or should it be using a more
thread-safe method like a rwlock or condition variable?

Maybe the device state structure is already protected at some level
with a mutex - just not obvious to me from these changes...

Thanks,

Darren.

On Friday, 2022-05-27 at 12:19:35 -04, Alexander Bulekov wrote:
> Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
> This flag should be set/checked prior to calling a device's MemoryRegion
> handlers, and set when device code initiates DMA.  The purpose of this
> flag is to prevent DMA reentrancy issues. E.g.:
> sdhci pio -> dma write -> sdhci mmio
> nvme bh -> dma write -> nvme mmio
>
> These issues have led to problems such as stack-exhaustion and
> use-after-frees.
>
> Assumptions:
>  * Devices do not interact with their own PIO/MMIO memory-regions using
>    DMA.
>
>  * There is now way for there to be multiple simultaneous accesses to a
>    device's PIO/MMIO memory-regions, or for multiple threads to perform
>    DMA accesses simultaneously on behalf of a single device.
>
> Signed-off-by: Alexander Bulekov <alxndr@bu.edu>
> ---
>  include/hw/qdev-core.h | 3 +++
>  1 file changed, 3 insertions(+)
>
> diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
> index 92c3d65208..6474dc51fa 100644
> --- a/include/hw/qdev-core.h
> +++ b/include/hw/qdev-core.h
> @@ -193,6 +193,9 @@ struct DeviceState {
>      int instance_id_alias;
>      int alias_required_for_version;
>      ResettableState reset;
> +
> +    /* Is the device currently in mmio/pio/dma? Used to prevent re-entrancy */
> +    int engaged_in_io;
>  };
>  
>  struct DeviceListener {
> -- 
> 2.33.0
Peter Maydell May 30, 2022, 11:19 a.m. UTC | #2
On Fri, 27 May 2022 at 17:19, Alexander Bulekov <alxndr@bu.edu> wrote:
>
> Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
> This flag should be set/checked prior to calling a device's MemoryRegion
> handlers, and set when device code initiates DMA.  The purpose of this
> flag is to prevent DMA reentrancy issues. E.g.:
> sdhci pio -> dma write -> sdhci mmio
> nvme bh -> dma write -> nvme mmio
>
> These issues have led to problems such as stack-exhaustion and
> use-after-frees.
>
> Assumptions:
>  * Devices do not interact with their own PIO/MMIO memory-regions using
>    DMA.

If you're trying to protect against malicious guest-controlled
DMA operations, you can't assume that. The guest can program
a DMA controller to DMA to its own MMIO register bank if it likes.

>  * There is now way for there to be multiple simultaneous accesses to a
>    device's PIO/MMIO memory-regions, or for multiple threads to perform
>    DMA accesses simultaneously on behalf of a single device.

This one is generally true because device code runs with
the iothread lock held.

-- PMM
David Hildenbrand May 30, 2022, 12:13 p.m. UTC | #3
On 27.05.22 18:19, Alexander Bulekov wrote:
> Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
> This flag should be set/checked prior to calling a device's MemoryRegion
> handlers, and set when device code initiates DMA.  The purpose of this
> flag is to prevent DMA reentrancy issues. E.g.:
> sdhci pio -> dma write -> sdhci mmio
> nvme bh -> dma write -> nvme mmio
> 
> These issues have led to problems such as stack-exhaustion and
> use-after-frees.
> 
> Assumptions:
>  * Devices do not interact with their own PIO/MMIO memory-regions using
>    DMA.
> 
>  * There is now way for there to be multiple simultaneous accesses to a
>    device's PIO/MMIO memory-regions, or for multiple threads to perform
>    DMA accesses simultaneously on behalf of a single device.
> 
> Signed-off-by: Alexander Bulekov <alxndr@bu.edu>

I think this patch should be squashed into the other ones, it doesn't
make particular sense without any actual users.
Alexander Bulekov May 30, 2022, 1:09 p.m. UTC | #4
On 220530 1219, Peter Maydell wrote:
> On Fri, 27 May 2022 at 17:19, Alexander Bulekov <alxndr@bu.edu> wrote:
> >
> > Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
> > This flag should be set/checked prior to calling a device's MemoryRegion
> > handlers, and set when device code initiates DMA.  The purpose of this
> > flag is to prevent DMA reentrancy issues. E.g.:
> > sdhci pio -> dma write -> sdhci mmio
> > nvme bh -> dma write -> nvme mmio
> >
> > These issues have led to problems such as stack-exhaustion and
> > use-after-frees.
> >
> > Assumptions:
> >  * Devices do not interact with their own PIO/MMIO memory-regions using
> >    DMA.
> 
> If you're trying to protect against malicious guest-controlled
> DMA operations, you can't assume that. The guest can program
> a DMA controller to DMA to its own MMIO register bank if it likes.

If this is the case, then it seems the only way to fix this class of
problems is to rewrite device code so that it is safe for re-entrancy.
That seems to require significant upfront work to support behavior that
is often not even specified.
Simply spot-fixing the fuzzer re-entracy bugs seems like a dangerous,
incomplete solution.

Can we disable re-entracy by default, to fix the security issues, and
allow device code to "opt-in" to re-entrancy?

-Alex

> 
> >  * There is now way for there to be multiple simultaneous accesses to a
> >    device's PIO/MMIO memory-regions, or for multiple threads to perform
> >    DMA accesses simultaneously on behalf of a single device.
> 
> This one is generally true because device code runs with
> the iothread lock held.
> 
> -- PMM
Peter Maydell May 30, 2022, 1:28 p.m. UTC | #5
On Mon, 30 May 2022 at 14:10, Alexander Bulekov <alxndr@bu.edu> wrote:
>
> On 220530 1219, Peter Maydell wrote:
> > On Fri, 27 May 2022 at 17:19, Alexander Bulekov <alxndr@bu.edu> wrote:
> > >
> > > Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
> > > This flag should be set/checked prior to calling a device's MemoryRegion
> > > handlers, and set when device code initiates DMA.  The purpose of this
> > > flag is to prevent DMA reentrancy issues. E.g.:
> > > sdhci pio -> dma write -> sdhci mmio
> > > nvme bh -> dma write -> nvme mmio
> > >
> > > These issues have led to problems such as stack-exhaustion and
> > > use-after-frees.
> > >
> > > Assumptions:
> > >  * Devices do not interact with their own PIO/MMIO memory-regions using
> > >    DMA.
> >
> > If you're trying to protect against malicious guest-controlled
> > DMA operations, you can't assume that. The guest can program
> > a DMA controller to DMA to its own MMIO register bank if it likes.
>
> If this is the case, then it seems the only way to fix this class of
> problems is to rewrite device code so that it is safe for re-entrancy.
> That seems to require significant upfront work to support behavior that
> is often not even specified.
> Simply spot-fixing the fuzzer re-entracy bugs seems like a dangerous,
> incomplete solution.
>
> Can we disable re-entracy by default, to fix the security issues, and
> allow device code to "opt-in" to re-entrancy?

That's a different question, ie "are there legitimate cases where
devices try to DMA to themselves?". I don't know the answer, but
I suspect not.

-- PMM
Philippe Mathieu-Daudé May 30, 2022, 1:39 p.m. UTC | #6
On 30/5/22 15:28, Peter Maydell wrote:
> On Mon, 30 May 2022 at 14:10, Alexander Bulekov <alxndr@bu.edu> wrote:
>>
>> On 220530 1219, Peter Maydell wrote:
>>> On Fri, 27 May 2022 at 17:19, Alexander Bulekov <alxndr@bu.edu> wrote:
>>>>
>>>> Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
>>>> This flag should be set/checked prior to calling a device's MemoryRegion
>>>> handlers, and set when device code initiates DMA.  The purpose of this
>>>> flag is to prevent DMA reentrancy issues. E.g.:
>>>> sdhci pio -> dma write -> sdhci mmio
>>>> nvme bh -> dma write -> nvme mmio
>>>>
>>>> These issues have led to problems such as stack-exhaustion and
>>>> use-after-frees.
>>>>
>>>> Assumptions:
>>>>   * Devices do not interact with their own PIO/MMIO memory-regions using
>>>>     DMA.
>>>
>>> If you're trying to protect against malicious guest-controlled
>>> DMA operations, you can't assume that. The guest can program
>>> a DMA controller to DMA to its own MMIO register bank if it likes.
>>
>> If this is the case, then it seems the only way to fix this class of
>> problems is to rewrite device code so that it is safe for re-entrancy.
>> That seems to require significant upfront work to support behavior that
>> is often not even specified.
>> Simply spot-fixing the fuzzer re-entracy bugs seems like a dangerous,
>> incomplete solution.
>>
>> Can we disable re-entracy by default, to fix the security issues, and
>> allow device code to "opt-in" to re-entrancy?
> 
> That's a different question, ie "are there legitimate cases where
> devices try to DMA to themselves?". I don't know the answer, but
> I suspect not.

There is a niche where it might not be legitimate, but it is (ab)used
and Paolo wants to keep such cases working. I already responded to
Alexander here:
https://lore.kernel.org/qemu-devel/380ea0e5-a006-c570-4ec8-d67e837547ee@redhat.com/
Alexander Bulekov May 30, 2022, 1:41 p.m. UTC | #7
On 220530 1428, Peter Maydell wrote:
> On Mon, 30 May 2022 at 14:10, Alexander Bulekov <alxndr@bu.edu> wrote:
> >
> > On 220530 1219, Peter Maydell wrote:
> > > On Fri, 27 May 2022 at 17:19, Alexander Bulekov <alxndr@bu.edu> wrote:
> > > >
> > > > Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
> > > > This flag should be set/checked prior to calling a device's MemoryRegion
> > > > handlers, and set when device code initiates DMA.  The purpose of this
> > > > flag is to prevent DMA reentrancy issues. E.g.:
> > > > sdhci pio -> dma write -> sdhci mmio
> > > > nvme bh -> dma write -> nvme mmio
> > > >
> > > > These issues have led to problems such as stack-exhaustion and
> > > > use-after-frees.
> > > >
> > > > Assumptions:
> > > >  * Devices do not interact with their own PIO/MMIO memory-regions using
> > > >    DMA.
> > >
> > > If you're trying to protect against malicious guest-controlled
> > > DMA operations, you can't assume that. The guest can program
> > > a DMA controller to DMA to its own MMIO register bank if it likes.
> >
> > If this is the case, then it seems the only way to fix this class of
> > problems is to rewrite device code so that it is safe for re-entrancy.
> > That seems to require significant upfront work to support behavior that
> > is often not even specified.
> > Simply spot-fixing the fuzzer re-entracy bugs seems like a dangerous,
> > incomplete solution.
> >
> > Can we disable re-entracy by default, to fix the security issues, and
> > allow device code to "opt-in" to re-entrancy?
> 
> That's a different question, ie "are there legitimate cases where
> devices try to DMA to themselves?". I don't know the answer, but
> I suspect not.

Ah, I worded the assumption incorrectly. Should be
 * There is no valid use-case for a device to interact with its own
   PIO/MMIO memory-regions using DMA.

> 
> -- PMM
Alexander Bulekov May 30, 2022, 2:04 p.m. UTC | #8
On 220530 1539, Philippe Mathieu-Daudé wrote:
> On 30/5/22 15:28, Peter Maydell wrote:
> > On Mon, 30 May 2022 at 14:10, Alexander Bulekov <alxndr@bu.edu> wrote:
> > > 
> > > On 220530 1219, Peter Maydell wrote:
> > > > On Fri, 27 May 2022 at 17:19, Alexander Bulekov <alxndr@bu.edu> wrote:
> > > > > 
> > > > > Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA.
> > > > > This flag should be set/checked prior to calling a device's MemoryRegion
> > > > > handlers, and set when device code initiates DMA.  The purpose of this
> > > > > flag is to prevent DMA reentrancy issues. E.g.:
> > > > > sdhci pio -> dma write -> sdhci mmio
> > > > > nvme bh -> dma write -> nvme mmio
> > > > > 
> > > > > These issues have led to problems such as stack-exhaustion and
> > > > > use-after-frees.
> > > > > 
> > > > > Assumptions:
> > > > >   * Devices do not interact with their own PIO/MMIO memory-regions using
> > > > >     DMA.
> > > > 
> > > > If you're trying to protect against malicious guest-controlled
> > > > DMA operations, you can't assume that. The guest can program
> > > > a DMA controller to DMA to its own MMIO register bank if it likes.
> > > 
> > > If this is the case, then it seems the only way to fix this class of
> > > problems is to rewrite device code so that it is safe for re-entrancy.
> > > That seems to require significant upfront work to support behavior that
> > > is often not even specified.
> > > Simply spot-fixing the fuzzer re-entracy bugs seems like a dangerous,
> > > incomplete solution.
> > > 
> > > Can we disable re-entracy by default, to fix the security issues, and
> > > allow device code to "opt-in" to re-entrancy?
> > 
> > That's a different question, ie "are there legitimate cases where
> > devices try to DMA to themselves?". I don't know the answer, but
> > I suspect not.
> 
> There is a niche where it might not be legitimate, but it is (ab)used
> and Paolo wants to keep such cases working. I already responded to
> Alexander here:
> https://lore.kernel.org/qemu-devel/380ea0e5-a006-c570-4ec8-d67e837547ee@redhat.com/

I'm not sure we confirmed that this is actually an example of a device
performing DMA to its own MMIO. Unless I am missing something, the BLOAD
example simply performs repeated writes to VRAM?

That said video-related devices seem like possible candidates where such
behavior is conceivable. But even in those cases, the memory regions
would likely be ram/rom devices (which are excluded from the re-entrancy
check).
diff mbox series

Patch

diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h
index 92c3d65208..6474dc51fa 100644
--- a/include/hw/qdev-core.h
+++ b/include/hw/qdev-core.h
@@ -193,6 +193,9 @@  struct DeviceState {
     int instance_id_alias;
     int alias_required_for_version;
     ResettableState reset;
+
+    /* Is the device currently in mmio/pio/dma? Used to prevent re-entrancy */
+    int engaged_in_io;
 };
 
 struct DeviceListener {