From: Prasad J Pandit <pjp@fedoraproject.org> Hello, * First patch fixes an OOB access issue which may occur when a guest user sets 'reply_queue_head' field to a negative or large positive value, via 'struct mfi_init_qinfo' object in megasas_init_firmware(), such that 'index' variables in megasas_lookup_frame() goes beyond the s->frames[MEGASAS_MAX_FRAMES=2048] array bounds. -> https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg03131.html * Second patch fixes a NULL pointer dereference issue which may occur if megasas_enqueue_frame() routine returns a NULL frame for a given 'frame_addr' address. -> https://bugs.launchpad.net/qemu/+bug/1878259 * Third patch updates other numeric fields of MegasasState to unsigned type. Thank you. -- Prasad J Pandit (3): megasas: use unsigned type for reply_queue_head and check index megasas: avoid NULL pointer dereference megasas: use unsigned type for positive numeric fields hw/scsi/megasas.c | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) -- 2.25.4
From: Prasad J Pandit <pjp@fedoraproject.org> A guest user may set 'reply_queue_head' field of MegasasState to a negative value. Later in 'megasas_lookup_frame' it is used to index into s->frames[] array. Use unsigned type to avoid OOB access issue. Also check that 'index' value stays within s->frames[] bounds through the while() loop in 'megasas_lookup_frame' to avoid OOB access. Reported-by: Ren Ding <rding@gatech.edu> Reported-by: Hanqing Zhao <hanqing@gatech.edu> Reported-by: Alexander Bulekov <alxndr@bu.edu> Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> --- hw/scsi/megasas.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) Update v1 -> v2: fix OOB access when index > MEGASAS_MAX_FRAMES(=2048) -> https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg03131.html diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c index af18c88b65..6ce598cd69 100644 --- a/hw/scsi/megasas.c +++ b/hw/scsi/megasas.c @@ -112,7 +112,7 @@ typedef struct MegasasState { uint64_t reply_queue_pa; void *reply_queue; int reply_queue_len; - int reply_queue_head; + uint16_t reply_queue_head; int reply_queue_tail; uint64_t consumer_pa; uint64_t producer_pa; @@ -445,7 +445,7 @@ static MegasasCmd *megasas_lookup_frame(MegasasState *s, index = s->reply_queue_head; - while (num < s->fw_cmds) { + while (num < s->fw_cmds && index < MEGASAS_MAX_FRAMES) { if (s->frames[index].pa && s->frames[index].pa == frame) { cmd = &s->frames[index]; break; -- 2.25.4
From: Prasad J Pandit <pjp@fedoraproject.org> While in megasas_handle_frame(), megasas_enqueue_frame() may set a NULL frame into MegasasCmd object for a given 'frame_addr' address. Add check to avoid a NULL pointer dereference issue. Reported-by: Alexander Bulekov <alxndr@bu.edu> Fixes: https://bugs.launchpad.net/qemu/+bug/1878259 Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> --- hw/scsi/megasas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c index 6ce598cd69..b531d88a9b 100644 --- a/hw/scsi/megasas.c +++ b/hw/scsi/megasas.c @@ -504,7 +504,7 @@ static MegasasCmd *megasas_enqueue_frame(MegasasState *s, cmd->pa = frame; /* Map all possible frames */ cmd->frame = pci_dma_map(pcid, frame, &frame_size_p, 0); - if (frame_size_p != frame_size) { + if (!cmd->frame || frame_size_p != frame_size) { trace_megasas_qf_map_failed(cmd->index, (unsigned long)frame); if (cmd->frame) { megasas_unmap_frame(s, cmd); -- 2.25.4
From: Prasad J Pandit <pjp@fedoraproject.org> Use unsigned type for the MegasasState fields which hold positive numeric values. Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> --- hw/scsi/megasas.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c index b531d88a9b..634af0bbb8 100644 --- a/hw/scsi/megasas.c +++ b/hw/scsi/megasas.c @@ -86,34 +86,34 @@ typedef struct MegasasState { MemoryRegion queue_io; uint32_t frame_hi; - int fw_state; + uint32_t fw_state; uint32_t fw_sge; uint32_t fw_cmds; uint32_t flags; - int fw_luns; - int intr_mask; - int doorbell; - int busy; - int diag; - int adp_reset; + uint32_t fw_luns; + uint32_t intr_mask; + uint32_t doorbell; + uint32_t busy; + uint32_t diag; + uint32_t adp_reset; OnOffAuto msi; OnOffAuto msix; MegasasCmd *event_cmd; - int event_locale; + uint16_t event_locale; int event_class; - int event_count; - int shutdown_event; - int boot_event; + uint32_t event_count; + uint32_t shutdown_event; + uint32_t boot_event; uint64_t sas_addr; char *hba_serial; uint64_t reply_queue_pa; void *reply_queue; - int reply_queue_len; + uint16_t reply_queue_len; uint16_t reply_queue_head; - int reply_queue_tail; + uint16_t reply_queue_tail; uint64_t consumer_pa; uint64_t producer_pa; @@ -2259,9 +2259,9 @@ static const VMStateDescription vmstate_megasas_gen1 = { VMSTATE_PCI_DEVICE(parent_obj, MegasasState), VMSTATE_MSIX(parent_obj, MegasasState), - VMSTATE_INT32(fw_state, MegasasState), - VMSTATE_INT32(intr_mask, MegasasState), - VMSTATE_INT32(doorbell, MegasasState), + VMSTATE_UINT32(fw_state, MegasasState), + VMSTATE_UINT32(intr_mask, MegasasState), + VMSTATE_UINT32(doorbell, MegasasState), VMSTATE_UINT64(reply_queue_pa, MegasasState), VMSTATE_UINT64(consumer_pa, MegasasState), VMSTATE_UINT64(producer_pa, MegasasState), @@ -2278,9 +2278,9 @@ static const VMStateDescription vmstate_megasas_gen2 = { VMSTATE_PCI_DEVICE(parent_obj, MegasasState), VMSTATE_MSIX(parent_obj, MegasasState), - VMSTATE_INT32(fw_state, MegasasState), - VMSTATE_INT32(intr_mask, MegasasState), - VMSTATE_INT32(doorbell, MegasasState), + VMSTATE_UINT32(fw_state, MegasasState), + VMSTATE_UINT32(intr_mask, MegasasState), + VMSTATE_UINT32(doorbell, MegasasState), VMSTATE_UINT64(reply_queue_pa, MegasasState), VMSTATE_UINT64(consumer_pa, MegasasState), VMSTATE_UINT64(producer_pa, MegasasState), -- 2.25.4
On 200514 0055, P J P wrote: > From: Prasad J Pandit <pjp@fedoraproject.org> > > While in megasas_handle_frame(), megasas_enqueue_frame() may > set a NULL frame into MegasasCmd object for a given 'frame_addr' > address. Add check to avoid a NULL pointer dereference issue. > > Reported-by: Alexander Bulekov <alxndr@bu.edu> > Fixes: https://bugs.launchpad.net/qemu/+bug/1878259 > Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> Acked-by: Alexander Bulekov <alxndr@bu.edu> > --- > hw/scsi/megasas.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c > index 6ce598cd69..b531d88a9b 100644 > --- a/hw/scsi/megasas.c > +++ b/hw/scsi/megasas.c > @@ -504,7 +504,7 @@ static MegasasCmd *megasas_enqueue_frame(MegasasState *s, > cmd->pa = frame; > /* Map all possible frames */ > cmd->frame = pci_dma_map(pcid, frame, &frame_size_p, 0); > - if (frame_size_p != frame_size) { > + if (!cmd->frame || frame_size_p != frame_size) { > trace_megasas_qf_map_failed(cmd->index, (unsigned long)frame); > if (cmd->frame) { > megasas_unmap_frame(s, cmd); > -- > 2.25.4 >
Hi Prasad, On 200514 0055, P J P wrote: > From: Prasad J Pandit <pjp@fedoraproject.org> > > A guest user may set 'reply_queue_head' field of MegasasState to > a negative value. Later in 'megasas_lookup_frame' it is used to > index into s->frames[] array. Use unsigned type to avoid OOB > access issue. > > Also check that 'index' value stays within s->frames[] bounds > through the while() loop in 'megasas_lookup_frame' to avoid OOB > access. > > Reported-by: Ren Ding <rding@gatech.edu> > Reported-by: Hanqing Zhao <hanqing@gatech.edu> > Reported-by: Alexander Bulekov <alxndr@bu.edu> > Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> > --- Acked-by: Alexander Bulekov <alxndr@bu.edu> I applied these patches and could not reproduce the heap-overflow, or LP1878259 Thanks -Alex > hw/scsi/megasas.c | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > > Update v1 -> v2: fix OOB access when index > MEGASAS_MAX_FRAMES(=2048) > -> https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg03131.html > > diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c > index af18c88b65..6ce598cd69 100644 > --- a/hw/scsi/megasas.c > +++ b/hw/scsi/megasas.c > @@ -112,7 +112,7 @@ typedef struct MegasasState { > uint64_t reply_queue_pa; > void *reply_queue; > int reply_queue_len; > - int reply_queue_head; > + uint16_t reply_queue_head; > int reply_queue_tail; > uint64_t consumer_pa; > uint64_t producer_pa; > @@ -445,7 +445,7 @@ static MegasasCmd *megasas_lookup_frame(MegasasState *s, > > index = s->reply_queue_head; > > - while (num < s->fw_cmds) { > + while (num < s->fw_cmds && index < MEGASAS_MAX_FRAMES) { > if (s->frames[index].pa && s->frames[index].pa == frame) { > cmd = &s->frames[index]; > break; > -- > 2.25.4 >
Hi Prasad, On Thursday, 2020-05-14 at 00:55:38 +0530, P J P wrote: > From: Prasad J Pandit <pjp@fedoraproject.org> > > A guest user may set 'reply_queue_head' field of MegasasState to > a negative value. Later in 'megasas_lookup_frame' it is used to > index into s->frames[] array. Use unsigned type to avoid OOB > access issue. > > Also check that 'index' value stays within s->frames[] bounds > through the while() loop in 'megasas_lookup_frame' to avoid OOB > access. > > Reported-by: Ren Ding <rding@gatech.edu> > Reported-by: Hanqing Zhao <hanqing@gatech.edu> > Reported-by: Alexander Bulekov <alxndr@bu.edu> > Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> > --- > hw/scsi/megasas.c | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > > Update v1 -> v2: fix OOB access when index > MEGASAS_MAX_FRAMES(=2048) > -> https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg03131.html > > diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c > index af18c88b65..6ce598cd69 100644 > --- a/hw/scsi/megasas.c > +++ b/hw/scsi/megasas.c > @@ -112,7 +112,7 @@ typedef struct MegasasState { > uint64_t reply_queue_pa; > void *reply_queue; > int reply_queue_len; > - int reply_queue_head; > + uint16_t reply_queue_head; > int reply_queue_tail; > uint64_t consumer_pa; > uint64_t producer_pa; > @@ -445,7 +445,7 @@ static MegasasCmd *megasas_lookup_frame(MegasasState *s, > > index = s->reply_queue_head; While it is probably unlikely that it would cause an integer underflow here, for consistency, index probably should also be declared as unsigned, but from what I can tell it is still an 'int'. Thanks, Darren. > > - while (num < s->fw_cmds) { > + while (num < s->fw_cmds && index < MEGASAS_MAX_FRAMES) { > if (s->frames[index].pa && s->frames[index].pa == frame) { > cmd = &s->frames[index]; > break; > -- > 2.25.4
On Thursday, 2020-05-14 at 00:55:39 +0530, P J P wrote: > From: Prasad J Pandit <pjp@fedoraproject.org> > > While in megasas_handle_frame(), megasas_enqueue_frame() may > set a NULL frame into MegasasCmd object for a given 'frame_addr' > address. Add check to avoid a NULL pointer dereference issue. > > Reported-by: Alexander Bulekov <alxndr@bu.edu> > Fixes: https://bugs.launchpad.net/qemu/+bug/1878259 > Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> Reviewed-by: Darren Kenny <darren.kenny@oracle.com> > --- > hw/scsi/megasas.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c > index 6ce598cd69..b531d88a9b 100644 > --- a/hw/scsi/megasas.c > +++ b/hw/scsi/megasas.c > @@ -504,7 +504,7 @@ static MegasasCmd *megasas_enqueue_frame(MegasasState *s, > cmd->pa = frame; > /* Map all possible frames */ > cmd->frame = pci_dma_map(pcid, frame, &frame_size_p, 0); > - if (frame_size_p != frame_size) { > + if (!cmd->frame || frame_size_p != frame_size) { > trace_megasas_qf_map_failed(cmd->index, (unsigned long)frame); > if (cmd->frame) { > megasas_unmap_frame(s, cmd); > -- > 2.25.4
On Thursday, 2020-05-14 at 00:55:40 +0530, P J P wrote: > From: Prasad J Pandit <pjp@fedoraproject.org> > > Use unsigned type for the MegasasState fields which hold positive > numeric values. > > Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org> Reviewed-by: Darren Kenny <darren.kenny@oracle.com> > --- > hw/scsi/megasas.c | 38 +++++++++++++++++++------------------- > 1 file changed, 19 insertions(+), 19 deletions(-) > > diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c > index b531d88a9b..634af0bbb8 100644 > --- a/hw/scsi/megasas.c > +++ b/hw/scsi/megasas.c > @@ -86,34 +86,34 @@ typedef struct MegasasState { > MemoryRegion queue_io; > uint32_t frame_hi; > > - int fw_state; > + uint32_t fw_state; > uint32_t fw_sge; > uint32_t fw_cmds; > uint32_t flags; > - int fw_luns; > - int intr_mask; > - int doorbell; > - int busy; > - int diag; > - int adp_reset; > + uint32_t fw_luns; > + uint32_t intr_mask; > + uint32_t doorbell; > + uint32_t busy; > + uint32_t diag; > + uint32_t adp_reset; > OnOffAuto msi; > OnOffAuto msix; > > MegasasCmd *event_cmd; > - int event_locale; > + uint16_t event_locale; > int event_class; > - int event_count; > - int shutdown_event; > - int boot_event; > + uint32_t event_count; > + uint32_t shutdown_event; > + uint32_t boot_event; > > uint64_t sas_addr; > char *hba_serial; > > uint64_t reply_queue_pa; > void *reply_queue; > - int reply_queue_len; > + uint16_t reply_queue_len; > uint16_t reply_queue_head; > - int reply_queue_tail; > + uint16_t reply_queue_tail; > uint64_t consumer_pa; > uint64_t producer_pa; > > @@ -2259,9 +2259,9 @@ static const VMStateDescription vmstate_megasas_gen1 = { > VMSTATE_PCI_DEVICE(parent_obj, MegasasState), > VMSTATE_MSIX(parent_obj, MegasasState), > > - VMSTATE_INT32(fw_state, MegasasState), > - VMSTATE_INT32(intr_mask, MegasasState), > - VMSTATE_INT32(doorbell, MegasasState), > + VMSTATE_UINT32(fw_state, MegasasState), > + VMSTATE_UINT32(intr_mask, MegasasState), > + VMSTATE_UINT32(doorbell, MegasasState), > VMSTATE_UINT64(reply_queue_pa, MegasasState), > VMSTATE_UINT64(consumer_pa, MegasasState), > VMSTATE_UINT64(producer_pa, MegasasState), > @@ -2278,9 +2278,9 @@ static const VMStateDescription vmstate_megasas_gen2 = { > VMSTATE_PCI_DEVICE(parent_obj, MegasasState), > VMSTATE_MSIX(parent_obj, MegasasState), > > - VMSTATE_INT32(fw_state, MegasasState), > - VMSTATE_INT32(intr_mask, MegasasState), > - VMSTATE_INT32(doorbell, MegasasState), > + VMSTATE_UINT32(fw_state, MegasasState), > + VMSTATE_UINT32(intr_mask, MegasasState), > + VMSTATE_UINT32(doorbell, MegasasState), > VMSTATE_UINT64(reply_queue_pa, MegasasState), > VMSTATE_UINT64(consumer_pa, MegasasState), > VMSTATE_UINT64(producer_pa, MegasasState), > -- > 2.25.4
Hello Darren, +-- On Thu, 14 May 2020, Darren Kenny wrote --+ | > Update v1 -> v2: fix OOB access when index > MEGASAS_MAX_FRAMES(=2048) | > -> https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg03131.html | > | > diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c | > - int reply_queue_head; | > + uint16_t reply_queue_head; | > @@ -445,7 +445,7 @@ static MegasasCmd *megasas_lookup_frame(MegasasState *s, | > | > index = s->reply_queue_head; | | While it is probably unlikely that it would cause an integer underflow | here, Yes, integer overflow is unlikely going from uint16_t -> to -> int type. s->reply_queue_head = ldl_le_pci_dma(pcid, s->producer_pa); s->reply_queue_head %= MEGASAS_MAX_FRAMES; Also here 's->reply_queue_head' is restricted between 0...MEGASAS_MAX_FRAMES=2048 | - while (num < s->fw_cmds) { | + while (num < s->fw_cmds && index < MEGASAS_MAX_FRAMES) { And this patch would help keep 'index' within the same 0..MEGASAS_MAX_FRAMES range. | for consistency, index probably should also be declared as unsigned, but | from what I can tell it is still an 'int'. It did cross my mind, but it's generally advised to keep these fixes to minimum possible changes and specific to the issue they fix. Index being a local variable, changing it to an unsigned type wouldn't help much to fix the issue or otherwise I think. Thank you. -- Prasad J Pandit / Red Hat Product Security Team 8685 545E B54C 486B C6EB 271E E285 8B5A F050 DE8D
On 13/05/20 21:25, P J P wrote:
> From: Prasad J Pandit <pjp@fedoraproject.org>
>
> While in megasas_handle_frame(), megasas_enqueue_frame() may
> set a NULL frame into MegasasCmd object for a given 'frame_addr'
> address. Add check to avoid a NULL pointer dereference issue.
>
> Reported-by: Alexander Bulekov <alxndr@bu.edu>
> Fixes: https://bugs.launchpad.net/qemu/+bug/1878259
> Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
> ---
> hw/scsi/megasas.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c
> index 6ce598cd69..b531d88a9b 100644
> --- a/hw/scsi/megasas.c
> +++ b/hw/scsi/megasas.c
> @@ -504,7 +504,7 @@ static MegasasCmd *megasas_enqueue_frame(MegasasState *s,
> cmd->pa = frame;
> /* Map all possible frames */
> cmd->frame = pci_dma_map(pcid, frame, &frame_size_p, 0);
> - if (frame_size_p != frame_size) {
> + if (!cmd->frame || frame_size_p != frame_size) {
> trace_megasas_qf_map_failed(cmd->index, (unsigned long)frame);
> if (cmd->frame) {
> megasas_unmap_frame(s, cmd);
> -- 2.25.4
>
I think the code here was expecting frame_size_p to be 0 if cmd->frame
is NULL. Can you check why this is not the case, or whether it ever was
the case?
Thanks,
Paolo
On 13/05/20 21:25, P J P wrote:
> From: Prasad J Pandit <pjp@fedoraproject.org>
>
> Hello,
>
> * First patch fixes an OOB access issue which may occur when a guest user
> sets 'reply_queue_head' field to a negative or large positive value,
> via 'struct mfi_init_qinfo' object in megasas_init_firmware(), such that
> 'index' variables in megasas_lookup_frame() goes beyond the
> s->frames[MEGASAS_MAX_FRAMES=2048] array bounds.
> -> https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg03131.html
>
> * Second patch fixes a NULL pointer dereference issue which may occur
> if megasas_enqueue_frame() routine returns a NULL frame for a given
> 'frame_addr' address.
> -> https://bugs.launchpad.net/qemu/+bug/1878259
>
> * Third patch updates other numeric fields of MegasasState to unsigned type.
>
> Thank you.
> --
> Prasad J Pandit (3):
> megasas: use unsigned type for reply_queue_head and check index
> megasas: avoid NULL pointer dereference
> megasas: use unsigned type for positive numeric fields
>
> hw/scsi/megasas.c | 44 ++++++++++++++++++++++----------------------
> 1 file changed, 22 insertions(+), 22 deletions(-)
>
> --
> 2.25.4
>
Queued, thanks (but see my comment on patch 2).
Thanks,
Paolo
Hello, +-- On Thu, 21 May 2020, Paolo Bonzini wrote --+ | I think the code here was expecting frame_size_p to be 0 if cmd->frame is | NULL. Can you check why this is not the case, or whether it ever was the | case? static MegasasCmd *megasas_enqueue_frame(MegasasState *s, hwaddr frame, ... int frame_size = MEGASAS_MAX_SGE * sizeof(union mfi_sgl); hwaddr frame_size_p = frame_size; <== = 128 * 16 = 2048 so 'frame_size_p' always starts with value '2048' ... cmd->frame = pci_dma_map(pcid, frame, &frame_size_p, 0); -> pci_dma_map -> dma_memory_map -> address_space_map mr = flatview_translate(fv, addr, &xlat, &l, is_write, attrs); ... if (atomic_xchg(&bounce.in_use, true)) { return NULL; <== NULL is returned from here } Later when address_space_map() returns 'NULL' above, '*plen' is not set to zero. diff --git a/exec.c b/exec.c index 5162f0d12f..4eea84bf66 100644 --- a/exec.c +++ b/exec.c @@ -3538,6 +3538,7 @@ void *address_space_map(AddressSpace *as, if (!memory_access_is_direct(mr, is_write)) { if (atomic_xchg(&bounce.in_use, true)) { + *plen = 0; return NULL; } I'll send a revised patch above. Thank you. -- Prasad J Pandit / Red Hat Product Security Team 8685 545E B54C 486B C6EB 271E E285 8B5A F050 DE8D
On 26/05/20 09:18, P J P wrote:
> Later when address_space_map() returns 'NULL' above, '*plen' is not set to
> zero.
>
> diff --git a/exec.c b/exec.c
> index 5162f0d12f..4eea84bf66 100644
> --- a/exec.c
> +++ b/exec.c
> @@ -3538,6 +3538,7 @@ void *address_space_map(AddressSpace *as,
>
> if (!memory_access_is_direct(mr, is_write)) {
> if (atomic_xchg(&bounce.in_use, true)) {
> + *plen = 0;
> return NULL;
> }
>
> I'll send a revised patch above.
Great, this looks good.
Paolo