[PATCH v4 9/9] scsi/scsi_bus: fix races in REPORT LUNS

Maxim Levitsky posted 9 patches 4 years, 2 months ago
Maintainers: Eduardo Habkost <ehabkost@redhat.com>, Fam Zheng <fam@euphon.net>, "Daniel P. Berrangé" <berrange@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>
There is a newer version of this series
[PATCH v4 9/9] scsi/scsi_bus: fix races in REPORT LUNS
Posted by Maxim Levitsky 4 years, 2 months ago
Currently scsi_target_emulate_report_luns iterates
over child devices list twice, and there is guarantee, that
it will not be changed meanwhile.

This reason for two loops is that it needs to know how much memory
to allocate.

Avoid this by iterating once, and allocating the memory for the output
dynamically with reserving enought memory so that in practice it won't
be reallocated often.

Bugzilla for reference: https://bugzilla.redhat.com/show_bug.cgi?id=1866707

Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
---
 hw/scsi/scsi-bus.c | 62 ++++++++++++++++++++++------------------------
 1 file changed, 29 insertions(+), 33 deletions(-)

diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c
index feab20b76d..150dee2e6a 100644
--- a/hw/scsi/scsi-bus.c
+++ b/hw/scsi/scsi-bus.c
@@ -438,19 +438,25 @@ struct SCSITargetReq {
 static void store_lun(uint8_t *outbuf, int lun)
 {
     if (lun < 256) {
+        /* Simple logical unit addressing method*/
+        outbuf[0] = 0;
         outbuf[1] = lun;
-        return;
+    } else {
+        /* Flat space addressing method */
+        outbuf[0] = 0x40 | (lun >> 8);
+        outbuf[1] = (lun & 255);
     }
-    outbuf[1] = (lun & 255);
-    outbuf[0] = (lun >> 8) | 0x40;
 }
 
 static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
 {
     BusChild *kid;
-    int i, len, n;
     int channel, id;
-    bool found_lun0;
+    uint8_t tmp[8] = {0};
+    int len = 0;
+
+    /* reserve space for 63 LUNs*/
+    GByteArray *buf = g_byte_array_sized_new(512);
 
     if (r->req.cmd.xfer < 16) {
         return false;
@@ -460,46 +466,36 @@ static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
     }
     channel = r->req.dev->channel;
     id = r->req.dev->id;
-    found_lun0 = false;
-    n = 0;
 
-    rcu_read_lock();
 
-    QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
-        DeviceState *qdev = kid->child;
-        SCSIDevice *dev = SCSI_DEVICE(qdev);
+    /* add size (will be updated later to correct value */
+    g_byte_array_append(buf, tmp, 8);
+    len += 8;
 
-        if (dev->channel == channel && dev->id == id) {
-            if (dev->lun == 0) {
-                found_lun0 = true;
-            }
-            n += 8;
-        }
-    }
-    if (!found_lun0) {
-        n += 8;
-    }
-
-    scsi_target_alloc_buf(&r->req, n + 8);
+    /* add LUN0 */
+    g_byte_array_append(buf, tmp, 8);
+    len += 8;
 
-    len = MIN(n + 8, r->req.cmd.xfer & ~7);
-    memset(r->buf, 0, len);
-    stl_be_p(&r->buf[0], n);
-    i = found_lun0 ? 8 : 16;
+    rcu_read_lock();
     QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
         DeviceState *qdev = kid->child;
         SCSIDevice *dev = SCSI_DEVICE(qdev);
 
-        if (dev->channel == channel && dev->id == id) {
-            store_lun(&r->buf[i], dev->lun);
-            i += 8;
+        if (dev->channel == channel && dev->id == id && dev->lun != 0) {
+            store_lun(tmp, dev->lun);
+            g_byte_array_append(buf, tmp, 8);
+            len += 8;
         }
     }
-
     rcu_read_unlock();
 
-    assert(i == n + 8);
-    r->len = len;
+    r->buf_len = len;
+    r->buf = g_byte_array_free(buf, FALSE);
+    r->len = MIN(len, r->req.cmd.xfer & ~7);
+
+    /* store the LUN list length */
+    stl_be_p(&r->buf[0], len - 8);
+
     return true;
 }
 
-- 
2.26.2


Re: [PATCH v4 9/9] scsi/scsi_bus: fix races in REPORT LUNS
Posted by Maxim Levitsky 4 years, 2 months ago
On Mon, 2020-08-31 at 18:01 +0300, Maxim Levitsky wrote:
> Currently scsi_target_emulate_report_luns iterates
> over child devices list twice, and there is guarantee, that
> it will not be changed meanwhile.
> 
> This reason for two loops is that it needs to know how much memory
> to allocate.
> 
> Avoid this by iterating once, and allocating the memory for the output
> dynamically with reserving enought memory so that in practice it won't
> be reallocated often.
Just too many spelling/grammar mistakes in the commit message. Sorry about that.

It should be something like that:

Currently scsi_target_emulate_report_luns iterates over the child device list
twice, and there is no guarantee that this list is the same in both iterations.

The reason for iterating twise is that the first iteration calculates 
how much memory to allocate.

However if we use a dynamic array we can avoid iterating twice, and therefore
we avoid this race.

Best regards,
	Maxim Levitsky


> 
> Bugzilla for reference: https://bugzilla.redhat.com/show_bug.cgi?id=1866707
> 
> Signed-off-by: Maxim Levitsky <mlevitsk@redhat.com>
> ---
>  hw/scsi/scsi-bus.c | 62 ++++++++++++++++++++++------------------------
>  1 file changed, 29 insertions(+), 33 deletions(-)
> 
> diff --git a/hw/scsi/scsi-bus.c b/hw/scsi/scsi-bus.c
> index feab20b76d..150dee2e6a 100644
> --- a/hw/scsi/scsi-bus.c
> +++ b/hw/scsi/scsi-bus.c
> @@ -438,19 +438,25 @@ struct SCSITargetReq {
>  static void store_lun(uint8_t *outbuf, int lun)
>  {
>      if (lun < 256) {
> +        /* Simple logical unit addressing method*/
> +        outbuf[0] = 0;
>          outbuf[1] = lun;
> -        return;
> +    } else {
> +        /* Flat space addressing method */
> +        outbuf[0] = 0x40 | (lun >> 8);
> +        outbuf[1] = (lun & 255);
>      }
> -    outbuf[1] = (lun & 255);
> -    outbuf[0] = (lun >> 8) | 0x40;
>  }
>  
>  static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
>  {
>      BusChild *kid;
> -    int i, len, n;
>      int channel, id;
> -    bool found_lun0;
> +    uint8_t tmp[8] = {0};
> +    int len = 0;
> +
> +    /* reserve space for 63 LUNs*/
> +    GByteArray *buf = g_byte_array_sized_new(512);
>  
>      if (r->req.cmd.xfer < 16) {
>          return false;
> @@ -460,46 +466,36 @@ static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
>      }
>      channel = r->req.dev->channel;
>      id = r->req.dev->id;
> -    found_lun0 = false;
> -    n = 0;
>  
> -    rcu_read_lock();
>  
> -    QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
> -        DeviceState *qdev = kid->child;
> -        SCSIDevice *dev = SCSI_DEVICE(qdev);
> +    /* add size (will be updated later to correct value */
> +    g_byte_array_append(buf, tmp, 8);
> +    len += 8;
>  
> -        if (dev->channel == channel && dev->id == id) {
> -            if (dev->lun == 0) {
> -                found_lun0 = true;
> -            }
> -            n += 8;
> -        }
> -    }
> -    if (!found_lun0) {
> -        n += 8;
> -    }
> -
> -    scsi_target_alloc_buf(&r->req, n + 8);
> +    /* add LUN0 */
> +    g_byte_array_append(buf, tmp, 8);
> +    len += 8;
>  
> -    len = MIN(n + 8, r->req.cmd.xfer & ~7);
> -    memset(r->buf, 0, len);
> -    stl_be_p(&r->buf[0], n);
> -    i = found_lun0 ? 8 : 16;
> +    rcu_read_lock();
>      QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
>          DeviceState *qdev = kid->child;
>          SCSIDevice *dev = SCSI_DEVICE(qdev);
>  
> -        if (dev->channel == channel && dev->id == id) {
> -            store_lun(&r->buf[i], dev->lun);
> -            i += 8;
> +        if (dev->channel == channel && dev->id == id && dev->lun != 0) {
> +            store_lun(tmp, dev->lun);
> +            g_byte_array_append(buf, tmp, 8);
> +            len += 8;
>          }
>      }
> -
>      rcu_read_unlock();
>  
> -    assert(i == n + 8);
> -    r->len = len;
> +    r->buf_len = len;
> +    r->buf = g_byte_array_free(buf, FALSE);
> +    r->len = MIN(len, r->req.cmd.xfer & ~7);
> +
> +    /* store the LUN list length */
> +    stl_be_p(&r->buf[0], len - 8);
> +
>      return true;
>  }
>  



Re: [PATCH v4 9/9] scsi/scsi_bus: fix races in REPORT LUNS
Posted by Stefan Hajnoczi 4 years, 2 months ago
On Mon, Aug 31, 2020 at 06:01:24PM +0300, Maxim Levitsky wrote:
> Currently scsi_target_emulate_report_luns iterates
> over child devices list twice, and there is guarantee, that
> it will not be changed meanwhile.
> 
> This reason for two loops is that it needs to know how much memory
> to allocate.
> 
> Avoid this by iterating once, and allocating the memory for the output
> dynamically with reserving enought memory so that in practice it won't
> be reallocated often.
> 
> Bugzilla for reference: https://bugzilla.redhat.com/show_bug.cgi?id=1866707

"Buglink:" is the tag name documented in
https://wiki.qemu.org/Contribute/SubmitAPatch#Write_a_meaningful_commit_message

>  static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
>  {
>      BusChild *kid;
> -    int i, len, n;
>      int channel, id;
> -    bool found_lun0;
> +    uint8_t tmp[8] = {0};
> +    int len = 0;
> +
> +    /* reserve space for 63 LUNs*/
> +    GByteArray *buf = g_byte_array_sized_new(512);
>  
>      if (r->req.cmd.xfer < 16) {
>          return false;

buf is leaked.

> @@ -460,46 +466,36 @@ static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
>      }
>      channel = r->req.dev->channel;
>      id = r->req.dev->id;
> -    found_lun0 = false;
> -    n = 0;
>  
> -    rcu_read_lock();
>  
> -    QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
> -        DeviceState *qdev = kid->child;
> -        SCSIDevice *dev = SCSI_DEVICE(qdev);
> +    /* add size (will be updated later to correct value */
> +    g_byte_array_append(buf, tmp, 8);
> +    len += 8;

Can g_byte_array_size() be used instead of keeping a len local variable?

Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
Re: [PATCH v4 9/9] scsi/scsi_bus: fix races in REPORT LUNS
Posted by Maxim Levitsky 4 years, 2 months ago
On Tue, 2020-09-08 at 16:27 +0100, Stefan Hajnoczi wrote:
> On Mon, Aug 31, 2020 at 06:01:24PM +0300, Maxim Levitsky wrote:
> > Currently scsi_target_emulate_report_luns iterates
> > over child devices list twice, and there is guarantee, that
> > it will not be changed meanwhile.
> > 
> > This reason for two loops is that it needs to know how much memory
> > to allocate.
> > 
> > Avoid this by iterating once, and allocating the memory for the output
> > dynamically with reserving enought memory so that in practice it won't
> > be reallocated often.
> > 
> > Bugzilla for reference: https://bugzilla.redhat.com/show_bug.cgi?id=1866707
> 
> "Buglink:" is the tag name documented in
> https://wiki.qemu.org/Contribute/SubmitAPatch#Write_a_meaningful_commit_message
Noted
> 
> >  static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
> >  {
> >      BusChild *kid;
> > -    int i, len, n;
> >      int channel, id;
> > -    bool found_lun0;
> > +    uint8_t tmp[8] = {0};
> > +    int len = 0;
> > +
> > +    /* reserve space for 63 LUNs*/
> > +    GByteArray *buf = g_byte_array_sized_new(512);
> >  
> >      if (r->req.cmd.xfer < 16) {
> >          return false;
> 
> buf is leaked.
Oops, will fix
> 
> > @@ -460,46 +466,36 @@ static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
> >      }
> >      channel = r->req.dev->channel;
> >      id = r->req.dev->id;
> > -    found_lun0 = false;
> > -    n = 0;
> >  
> > -    rcu_read_lock();
> >  
> > -    QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
> > -        DeviceState *qdev = kid->child;
> > -        SCSIDevice *dev = SCSI_DEVICE(qdev);
> > +    /* add size (will be updated later to correct value */
> > +    g_byte_array_append(buf, tmp, 8);
> > +    len += 8;
> 
> Can g_byte_array_size() be used instead of keeping a len local variable?
Glib don't seem to have this function, I checked the docs.
Its seems that they want to convert it to GBytes which is basically immutible verion
of GByteArray and it does have g_bytes_get_size.
I decided that a local variable while ugly is still better that this.


I haven't wrote much code that uses Glib, so I might have missed something though.
I had read this reference:
https://developer.gnome.org/glib/stable/glib-Byte-Arrays.html


> 
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>

Best regards,
	Maxim Levitsky



Re: [PATCH v4 9/9] scsi/scsi_bus: fix races in REPORT LUNS
Posted by Stefan Hajnoczi 4 years, 2 months ago
On Wed, Sep 09, 2020 at 11:20:24AM +0300, Maxim Levitsky wrote:
> On Tue, 2020-09-08 at 16:27 +0100, Stefan Hajnoczi wrote:
> > On Mon, Aug 31, 2020 at 06:01:24PM +0300, Maxim Levitsky wrote:
> > > @@ -460,46 +466,36 @@ static bool scsi_target_emulate_report_luns(SCSITargetReq *r)
> > >      }
> > >      channel = r->req.dev->channel;
> > >      id = r->req.dev->id;
> > > -    found_lun0 = false;
> > > -    n = 0;
> > >  
> > > -    rcu_read_lock();
> > >  
> > > -    QTAILQ_FOREACH_RCU(kid, &r->req.bus->qbus.children, sibling) {
> > > -        DeviceState *qdev = kid->child;
> > > -        SCSIDevice *dev = SCSI_DEVICE(qdev);
> > > +    /* add size (will be updated later to correct value */
> > > +    g_byte_array_append(buf, tmp, 8);
> > > +    len += 8;
> > 
> > Can g_byte_array_size() be used instead of keeping a len local variable?
> Glib don't seem to have this function, I checked the docs.
> Its seems that they want to convert it to GBytes which is basically immutible verion
> of GByteArray and it does have g_bytes_get_size.
> I decided that a local variable while ugly is still better that this.
> 
> 
> I haven't wrote much code that uses Glib, so I might have missed something though.
> I had read this reference:
> https://developer.gnome.org/glib/stable/glib-Byte-Arrays.html

Oops, you're right. GByteArray != GBytes. The local variable makes sense.

Stefan