diff mbox

check for available room when formatting OpenFirmware device path

Message ID 1343043213-9997-1-git-send-email-lersek@redhat.com
State New
Headers show

Commit Message

Laszlo Ersek July 23, 2012, 11:33 a.m. UTC
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
---
 hw/qdev.c |   14 +++++++++++++-
 vl.c      |    7 ++++++-
 2 files changed, 19 insertions(+), 2 deletions(-)

Comments

Peter Maydell July 23, 2012, 12:34 p.m. UTC | #1
On 23 July 2012 12:33, Laszlo Ersek <lersek@redhat.com> wrote:
>
> Signed-off-by: Laszlo Ersek <lersek@redhat.com>

I think it would be much nicer to just rewrite qdev_get_fw_dev_path
so we weren't trying to fill the path into a fixed string buffer
at all. Here is an entirely untested implementation:

char *qdev_get_fw_dev_path(DeviceState *dev)
{
    char *path;
    char **strarray;
    int depth = 0;
    DeviceState *d = dev;
    for (d = dev; d && d->parent_bus; d = d->parent_bus->parent) {
        depth++;
    }
    depth++;
    strarray = g_new(char*, depth);
    for (d = dev; d && d->parent_bus; d = d->parent_bus->parent) {
        depth--;
        strarray[depth] = bus_get_fw_dev_path(dev->parent_bus, dev);
        if (!strarray[depth]) {
            strarray[depth] = g_strdup(object_get_typename(OBJECT(dev)));
        }
    }
    strarray[0] = g_strdup("");
    path = g_strjoinv("/", strarray);
    g_strfreev(strarray);
    return path;
}

Bonus extra patch: check that all the implementations of get_fw_dev_path()
are returning a g_malloc'd string rather than a plain malloc'd one
(both this code and the current implementation assume so but at least
the scsi bus implementation doesn't use the glib functions.)

-- PMM
Peter Maydell July 23, 2012, 12:39 p.m. UTC | #2
On 23 July 2012 13:34, Peter Maydell <peter.maydell@linaro.org> wrote:
> On 23 July 2012 12:33, Laszlo Ersek <lersek@redhat.com> wrote:
>>
>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>
> I think it would be much nicer to just rewrite qdev_get_fw_dev_path
> so we weren't trying to fill the path into a fixed string buffer
> at all. Here is an entirely untested implementation:
>
> char *qdev_get_fw_dev_path(DeviceState *dev)
> {
>     char *path;
>     char **strarray;
>     int depth = 0;
>     DeviceState *d = dev;
>     for (d = dev; d && d->parent_bus; d = d->parent_bus->parent) {
>         depth++;
>     }
>     depth++;
>     strarray = g_new(char*, depth);
>     for (d = dev; d && d->parent_bus; d = d->parent_bus->parent) {
>         depth--;
>         strarray[depth] = bus_get_fw_dev_path(dev->parent_bus, dev);

"d" not "dev" here and in the line below, obviously. I said it was
untested :-)

>         if (!strarray[depth]) {
>             strarray[depth] = g_strdup(object_get_typename(OBJECT(dev)));
>         }
>     }
>     strarray[0] = g_strdup("");
>     path = g_strjoinv("/", strarray);
>     g_strfreev(strarray);
>     return path;
> }

-- PMM
Markus Armbruster July 23, 2012, 12:46 p.m. UTC | #3
Laszlo Ersek <lersek@redhat.com> writes:

> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
> ---
>  hw/qdev.c |   14 +++++++++++++-
>  vl.c      |    7 ++++++-
>  2 files changed, 19 insertions(+), 2 deletions(-)
>
> diff --git a/hw/qdev.c b/hw/qdev.c
> index af54467..f1e83a4 100644
> --- a/hw/qdev.c
> +++ b/hw/qdev.c
> @@ -502,6 +502,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>      if (dev && dev->parent_bus) {
>          char *d;
>          l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
> +        if (l >= size) {
> +            return l;
> +        }
> +
>          d = bus_get_fw_dev_path(dev->parent_bus, dev);
>          if (d) {
>              l += snprintf(p + l, size - l, "%s", d);
> @@ -509,6 +513,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>          } else {
>              l += snprintf(p + l, size - l, "%s", object_get_typename(OBJECT(dev)));
>          }
> +
> +        if (l >= size) {
> +            return l;
> +        }
>      }
>      l += snprintf(p + l , size - l, "/");
>  

If the return value is less than the size argument, it's the length of
the string written into p[].  Else, it means p[] has insufficient
space.

> @@ -520,8 +528,12 @@ char* qdev_get_fw_dev_path(DeviceState *dev)
>      char path[128];
>      int l;
>  
> -    l = qdev_get_fw_dev_path_helper(dev, path, 128);
> +    l = qdev_get_fw_dev_path_helper(dev, path, sizeof(path));
>  
> +    assert(l > 0);
> +    if (l >= sizeof(path)) {
> +        return NULL;
> +    }

Failure mode could be avoided with the common technique: make
qdev_get_fw_dev_path_helper() return the true length.  If it fit into
path[], return strdup(path).  Else, allocate a suitable buffer and try
again.

What do you think?

>      path[l-1] = '\0';
>  
>      return strdup(path);
> diff --git a/vl.c b/vl.c
> index 8904db1..78dcc93 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -913,7 +913,12 @@ char *get_boot_devices_list(uint32_t *size)
>  
>          if (i->dev) {
>              devpath = qdev_get_fw_dev_path(i->dev);
> -            assert(devpath);
> +            if (devpath == NULL) {
> +                fprintf(stderr,
> +                        "OpenFirmware Device Path too long (boot index %d)\n",
> +                        i->bootindex);
> +                exit(1);
> +            }
>          }
>  
>          if (i->suffix && devpath) {
Laszlo Ersek July 23, 2012, 1:08 p.m. UTC | #4
On 07/23/12 14:46, Markus Armbruster wrote:
> Laszlo Ersek <lersek@redhat.com> writes:
> 
>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>> ---
>>  hw/qdev.c |   14 +++++++++++++-
>>  vl.c      |    7 ++++++-
>>  2 files changed, 19 insertions(+), 2 deletions(-)
>>
>> diff --git a/hw/qdev.c b/hw/qdev.c
>> index af54467..f1e83a4 100644
>> --- a/hw/qdev.c
>> +++ b/hw/qdev.c
>> @@ -502,6 +502,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>      if (dev && dev->parent_bus) {
>>          char *d;
>>          l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
>> +        if (l >= size) {
>> +            return l;
>> +        }
>> +
>>          d = bus_get_fw_dev_path(dev->parent_bus, dev);
>>          if (d) {
>>              l += snprintf(p + l, size - l, "%s", d);
>> @@ -509,6 +513,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>          } else {
>>              l += snprintf(p + l, size - l, "%s", object_get_typename(OBJECT(dev)));
>>          }
>> +
>> +        if (l >= size) {
>> +            return l;
>> +        }
>>      }
>>      l += snprintf(p + l , size - l, "/");
>>  
> 
> If the return value is less than the size argument, it's the length of
> the string written into p[].  Else, it means p[] has insufficient
> space.

Yes. (snprintf() returns the number of bytes it would store, excluding
the terminating NUL, had there been enough room.
<http://pubs.opengroup.org/onlinepubs/9699919799/functions/snprintf.html#tag_16_159_04>)

Did I make a mistake?

Supposing snprintf() encounters no error, it returns a positive value P
in the above.

P = snprintf(..., size - l0, ...)
l1 = l0 + P;

         l1 >= size
<->  l0 + P >= size
<->       P >= size - l0


The return value of qdev_get_fw_dev_path_helper() comes from another
invocation of itself, or from the trailing snprintf(), so it behaves
like snprintf.

Or what do you have in mind?


> 
>> @@ -520,8 +528,12 @@ char* qdev_get_fw_dev_path(DeviceState *dev)
>>      char path[128];
>>      int l;
>>  
>> -    l = qdev_get_fw_dev_path_helper(dev, path, 128);
>> +    l = qdev_get_fw_dev_path_helper(dev, path, sizeof(path));
>>  
>> +    assert(l > 0);
>> +    if (l >= sizeof(path)) {
>> +        return NULL;
>> +    }
> 
> Failure mode could be avoided with the common technique: make
> qdev_get_fw_dev_path_helper() return the true length.  If it fit into
> path[], return strdup(path).  Else, allocate a suitable buffer and try
> again.
> 
> What do you think?

You're right of course, but I didn't have (or wanted to spend) the time
to change the code that much (and to test it...), especially because it
would have been fixed already if it had caused actual problems. I think
the patch could work as a "last resort", small improvement, but I don't
mind if someone posts a better fix :)

Laszlo
Markus Armbruster July 23, 2012, 3:01 p.m. UTC | #5
Laszlo Ersek <lersek@redhat.com> writes:

> On 07/23/12 14:46, Markus Armbruster wrote:
>> Laszlo Ersek <lersek@redhat.com> writes:
>> 
>>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>>> ---
>>>  hw/qdev.c |   14 +++++++++++++-
>>>  vl.c      |    7 ++++++-
>>>  2 files changed, 19 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/hw/qdev.c b/hw/qdev.c
>>> index af54467..f1e83a4 100644
>>> --- a/hw/qdev.c
>>> +++ b/hw/qdev.c
>>> @@ -502,6 +502,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>>      if (dev && dev->parent_bus) {
>>>          char *d;
>>>          l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
>>> +        if (l >= size) {
>>> +            return l;
>>> +        }
>>> +
>>>          d = bus_get_fw_dev_path(dev->parent_bus, dev);
>>>          if (d) {
>>>              l += snprintf(p + l, size - l, "%s", d);
>>> @@ -509,6 +513,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>>          } else {
>>>              l += snprintf(p + l, size - l, "%s", object_get_typename(OBJECT(dev)));
>>>          }
>>> +
>>> +        if (l >= size) {
>>> +            return l;
>>> +        }
>>>      }
>>>      l += snprintf(p + l , size - l, "/");
>>>  
>> 
>> If the return value is less than the size argument, it's the length of
>> the string written into p[].  Else, it means p[] has insufficient
>> space.
>
> Yes. (snprintf() returns the number of bytes it would store, excluding
> the terminating NUL, had there been enough room.
> <http://pubs.opengroup.org/onlinepubs/9699919799/functions/snprintf.html#tag_16_159_04>)
>
> Did I make a mistake?
>
> Supposing snprintf() encounters no error, it returns a positive value P
> in the above.
>
> P = snprintf(..., size - l0, ...)
> l1 = l0 + P;
>
>          l1 >= size
> <->  l0 + P >= size
> <->       P >= size - l0
>
>
> The return value of qdev_get_fw_dev_path_helper() comes from another
> invocation of itself, or from the trailing snprintf(), so it behaves
> like snprintf.
>
> Or what do you have in mind?

If I read your code correctly, qdev_get_fw_dev_path_helper() bails out
after the first snprintf() that goes beyond the buffer size.  When that
happens before the job's done, the return value is less than the length
of the full path.

 static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
 {
     int l = 0;

     if (dev && dev->parent_bus) {
         char *d;
         l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
+        if (l >= size) {
+            return l;                  <--- bail out early
+        }
+
         if (dev->parent_bus->info->get_fw_dev_path) {
             d = dev->parent_bus->info->get_fw_dev_path(dev);
             l += snprintf(p + l, size - l, "%s", d);
             qemu_free(d);
         } else {
             l += snprintf(p + l, size - l, "%s", dev->info->name);
         }
+
+        if (l >= size) {
+            return l;                  <--- bail out early
+        }
     }
     l += snprintf(p + l , size - l, "/");

     return l;
 }

>>> @@ -520,8 +528,12 @@ char* qdev_get_fw_dev_path(DeviceState *dev)
>>>      char path[128];
>>>      int l;
>>>  
>>> -    l = qdev_get_fw_dev_path_helper(dev, path, 128);
>>> +    l = qdev_get_fw_dev_path_helper(dev, path, sizeof(path));
>>>  
>>> +    assert(l > 0);
>>> +    if (l >= sizeof(path)) {
>>> +        return NULL;
>>> +    }
>> 
>> Failure mode could be avoided with the common technique: make
>> qdev_get_fw_dev_path_helper() return the true length.  If it fit into
>> path[], return strdup(path).  Else, allocate a suitable buffer and try
>> again.
>> 
>> What do you think?
>
> You're right of course, but I didn't have (or wanted to spend) the time
> to change the code that much (and to test it...), especially because it
> would have been fixed already if it had caused actual problems. I think
> the patch could work as a "last resort", small improvement, but I don't
> mind if someone posts a better fix :)

Only marginally harder than handling the failure :)
Laszlo Ersek July 23, 2012, 3:29 p.m. UTC | #6
On 07/23/12 17:01, Markus Armbruster wrote:
> Laszlo Ersek <lersek@redhat.com> writes:
> 
>> On 07/23/12 14:46, Markus Armbruster wrote:
>>> Laszlo Ersek <lersek@redhat.com> writes:
>>>
>>>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>>>> ---
>>>>  hw/qdev.c |   14 +++++++++++++-
>>>>  vl.c      |    7 ++++++-
>>>>  2 files changed, 19 insertions(+), 2 deletions(-)
>>>>
>>>> diff --git a/hw/qdev.c b/hw/qdev.c
>>>> index af54467..f1e83a4 100644
>>>> --- a/hw/qdev.c
>>>> +++ b/hw/qdev.c
>>>> @@ -502,6 +502,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>>>      if (dev && dev->parent_bus) {
>>>>          char *d;
>>>>          l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
>>>> +        if (l >= size) {
>>>> +            return l;
>>>> +        }
>>>> +
>>>>          d = bus_get_fw_dev_path(dev->parent_bus, dev);
>>>>          if (d) {
>>>>              l += snprintf(p + l, size - l, "%s", d);
>>>> @@ -509,6 +513,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>>>          } else {
>>>>              l += snprintf(p + l, size - l, "%s", object_get_typename(OBJECT(dev)));
>>>>          }
>>>> +
>>>> +        if (l >= size) {
>>>> +            return l;
>>>> +        }
>>>>      }
>>>>      l += snprintf(p + l , size - l, "/");
>>>>  
>>>
>>> If the return value is less than the size argument, it's the length of
>>> the string written into p[].  Else, it means p[] has insufficient
>>> space.
>>
>> Yes. (snprintf() returns the number of bytes it would store, excluding
>> the terminating NUL, had there been enough room.
>> <http://pubs.opengroup.org/onlinepubs/9699919799/functions/snprintf.html#tag_16_159_04>)
>>
>> Did I make a mistake?
>>
>> Supposing snprintf() encounters no error, it returns a positive value P
>> in the above.
>>
>> P = snprintf(..., size - l0, ...)
>> l1 = l0 + P;
>>
>>          l1 >= size
>> <->  l0 + P >= size
>> <->       P >= size - l0
>>
>>
>> The return value of qdev_get_fw_dev_path_helper() comes from another
>> invocation of itself, or from the trailing snprintf(), so it behaves
>> like snprintf.
>>
>> Or what do you have in mind?
> 
> If I read your code correctly, qdev_get_fw_dev_path_helper() bails out
> after the first snprintf() that goes beyond the buffer size.  When that
> happens before the job's done, the return value is less than the length
> of the full path.

Yes, but still greater than what would fit in the buffer. Is it a problem?

... Oh, did you mean the first comment in connection with the second?
Ie. we should continue to format (just for length counting's sake) and
then retry? IOW the early return isn't problematic in itself, but it
doesn't support the reallocation?

Thanks,
Laszlo
Markus Armbruster July 23, 2012, 3:42 p.m. UTC | #7
Laszlo Ersek <lersek@redhat.com> writes:

> On 07/23/12 17:01, Markus Armbruster wrote:
>> Laszlo Ersek <lersek@redhat.com> writes:
>> 
>>> On 07/23/12 14:46, Markus Armbruster wrote:
>>>> Laszlo Ersek <lersek@redhat.com> writes:
>>>>
>>>>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>>>>> ---
>>>>>  hw/qdev.c |   14 +++++++++++++-
>>>>>  vl.c      |    7 ++++++-
>>>>>  2 files changed, 19 insertions(+), 2 deletions(-)
>>>>>
>>>>> diff --git a/hw/qdev.c b/hw/qdev.c
>>>>> index af54467..f1e83a4 100644
>>>>> --- a/hw/qdev.c
>>>>> +++ b/hw/qdev.c
>>>>> @@ -502,6 +502,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>>>>      if (dev && dev->parent_bus) {
>>>>>          char *d;
>>>>>          l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
>>>>> +        if (l >= size) {
>>>>> +            return l;
>>>>> +        }
>>>>> +
>>>>>          d = bus_get_fw_dev_path(dev->parent_bus, dev);
>>>>>          if (d) {
>>>>>              l += snprintf(p + l, size - l, "%s", d);
>>>>> @@ -509,6 +513,10 @@ static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
>>>>>          } else {
>>>>>              l += snprintf(p + l, size - l, "%s", object_get_typename(OBJECT(dev)));
>>>>>          }
>>>>> +
>>>>> +        if (l >= size) {
>>>>> +            return l;
>>>>> +        }
>>>>>      }
>>>>>      l += snprintf(p + l , size - l, "/");
>>>>>  
>>>>
>>>> If the return value is less than the size argument, it's the length of
>>>> the string written into p[].  Else, it means p[] has insufficient
>>>> space.
>>>
>>> Yes. (snprintf() returns the number of bytes it would store, excluding
>>> the terminating NUL, had there been enough room.
>>> <http://pubs.opengroup.org/onlinepubs/9699919799/functions/snprintf.html#tag_16_159_04>)
>>>
>>> Did I make a mistake?
>>>
>>> Supposing snprintf() encounters no error, it returns a positive value P
>>> in the above.
>>>
>>> P = snprintf(..., size - l0, ...)
>>> l1 = l0 + P;
>>>
>>>          l1 >= size
>>> <->  l0 + P >= size
>>> <->       P >= size - l0
>>>
>>>
>>> The return value of qdev_get_fw_dev_path_helper() comes from another
>>> invocation of itself, or from the trailing snprintf(), so it behaves
>>> like snprintf.
>>>
>>> Or what do you have in mind?
>> 
>> If I read your code correctly, qdev_get_fw_dev_path_helper() bails out
>> after the first snprintf() that goes beyond the buffer size.  When that
>> happens before the job's done, the return value is less than the length
>> of the full path.
>
> Yes, but still greater than what would fit in the buffer. Is it a problem?
>
> ... Oh, did you mean the first comment in connection with the second?
> Ie. we should continue to format (just for length counting's sake) and
> then retry? IOW the early return isn't problematic in itself, but it
> doesn't support the reallocation?

Yes.

Your code lets the caller detect "need more space" reliably.  How much
more it can only guess, and need to be prepared for the retry to fail
again.  If we return the required size, like snprintf() does, the caller
knows, and the retry will succeed.
Laszlo Ersek July 25, 2012, 12:19 a.m. UTC | #8
As long as "two" qualifies as "assorted".

v1->v2:
- abandon original "idea", allocate sufficient memory for OFW device path
  formatting [Markus]
- all bus formatters should rely on glib for dynamic allocation [Peter]

Tested with an OVMF debug patch that grabs and logs the "bootorder" fw_cfg
file:

  /pci@i0cf8/ide@1,1/drive@0/disk@0
  /pci@i0cf8/ide@1,1/drive@1/disk@0
  /pci@i0cf8/isa@1/fdc@03f0/floppy@0
  /pci@i0cf8/ethernet@3/ethernet-phy@0

Laszlo Ersek (2):
  accomodate OpenFirmware device paths in sufficient storage
  get_fw_dev_path() impls should allocate memory with glib functions

 hw/ide/qdev.c |    2 +-
 hw/isa-bus.c  |    2 +-
 hw/pci.c      |    2 +-
 hw/qdev.c     |   54 +++++++++++++++++++++++++++++++++++++++++++-----------
 hw/scsi-bus.c |    2 +-
 hw/sysbus.c   |    2 +-
 6 files changed, 48 insertions(+), 16 deletions(-)
diff mbox

Patch

diff --git a/hw/qdev.c b/hw/qdev.c
index af54467..f1e83a4 100644
--- a/hw/qdev.c
+++ b/hw/qdev.c
@@ -502,6 +502,10 @@  static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
     if (dev && dev->parent_bus) {
         char *d;
         l = qdev_get_fw_dev_path_helper(dev->parent_bus->parent, p, size);
+        if (l >= size) {
+            return l;
+        }
+
         d = bus_get_fw_dev_path(dev->parent_bus, dev);
         if (d) {
             l += snprintf(p + l, size - l, "%s", d);
@@ -509,6 +513,10 @@  static int qdev_get_fw_dev_path_helper(DeviceState *dev, char *p, int size)
         } else {
             l += snprintf(p + l, size - l, "%s", object_get_typename(OBJECT(dev)));
         }
+
+        if (l >= size) {
+            return l;
+        }
     }
     l += snprintf(p + l , size - l, "/");
 
@@ -520,8 +528,12 @@  char* qdev_get_fw_dev_path(DeviceState *dev)
     char path[128];
     int l;
 
-    l = qdev_get_fw_dev_path_helper(dev, path, 128);
+    l = qdev_get_fw_dev_path_helper(dev, path, sizeof(path));
 
+    assert(l > 0);
+    if (l >= sizeof(path)) {
+        return NULL;
+    }
     path[l-1] = '\0';
 
     return strdup(path);
diff --git a/vl.c b/vl.c
index 8904db1..78dcc93 100644
--- a/vl.c
+++ b/vl.c
@@ -913,7 +913,12 @@  char *get_boot_devices_list(uint32_t *size)
 
         if (i->dev) {
             devpath = qdev_get_fw_dev_path(i->dev);
-            assert(devpath);
+            if (devpath == NULL) {
+                fprintf(stderr,
+                        "OpenFirmware Device Path too long (boot index %d)\n",
+                        i->bootindex);
+                exit(1);
+            }
         }
 
         if (i->suffix && devpath) {