qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] [PATCH] smbios: Add 1 terminator if there is any string


From: Laszlo Ersek
Subject: Re: [Qemu-devel] [PATCH] smbios: Add 1 terminator if there is any string field defined in given table.
Date: Thu, 10 Nov 2016 18:31:31 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Thunderbird/45.4.0

On 11/10/16 18:18, Laszlo Ersek wrote:
> On 11/10/16 16:09, Michael S. Tsirkin wrote:
>> On Tue, Sep 06, 2016 at 04:28:33PM +0800, Lin Ma wrote:
>>> If user specifies binary file on command line to load smbios entries, then
>>> will get error messages while decoding them in guest.
>>>
>>> Reproducer:
>>> 1. dump a smbios table to a binary file from host or guest.(says table 1)
>>> 2. load the binary file through command line: 'qemu -smbios file=...'.
>>> 3. perform 'dmidecode' or 'dmidecode -t 1' in guest.
>>>
>>> It reports 'Invalid entry length...' because qemu doesn't add terminator(s) 
>>> for
>>> the table correctly.
>>> For smbios tables which have string field provided, qemu should add 1 
>>> terminator.
>>> For smbios tables which dont have string field provided, qemu should add 2.
>>>
>>> This patch fixed the issue.
>>>
>>> Signed-off-by: Lin Ma <address@hidden>
>>
>> Seems to make sense superficially
>>
>> Acked-by: Michael S. Tsirkin <address@hidden>
>>
>> Fam, would you like to take this?
> 
> If we're not in a mortal hurry to enable QEMU to consume dmidecode
> output directly, can we please think about enabling dmidecode to dump
> binarily-correct tables?
> 
> The commit message doesn't mention, but in the dmidecode manual, I see
> "--dump-bin" and "--from-dump". Hm... The manual says,
> 
>>            --dump-bin FILE
>>               Do  not  decode the entries, instead dump the DMI data
>>               to a file in binary form. The generated file is  suit-
>>               able to pass to --from-dump later.
>>
>>            --from-dump FILE
>>               Read the DMI data from a binary file previously gener-
>>               ated using --dump-bin.
>> [...]
>>
>> BINARY DUMP FILE FORMAT
>>        The binary dump files generated by --dump-bin and read  using
>>        --from-dump are formatted as follows:
>>
>>        * The  SMBIOS  or  DMI entry point is located at offset 0x00.
>>          It is crafted to hard-code  the  table  address  at  offset
>>          0x20.
>>
>>        * The DMI table is located at offset 0x20.
> 
> Is this how the tables were dumped originally, in step 1?
> 
> Actually, the manual also says,
> 
>>        Options  --string, --type and --dump-bin determine the output
>>        format and are mutually exclusive.
> 
> Since step 1 mentions "say[] table 1", I think --dump-bin was not used.
> In that case however, dmidecode can only produce textual output, for
> example, hexadecimal output, with "--dump".
> 
> This means that some helper utility must have been used to turn the
> hexadecimal output into binary. Since the dmidecode output has to be
> post-processed anyway, I wonder if we should keep this data munging out
> of QEMU.
> 
> Anyway, I have some comments for the patch as well:
> 
> 
>>> ---
>>>  hw/smbios/smbios.c         | 90 
>>> ++++++++++++++++++++++++++++++++++++++++++++++
>>>  include/hw/smbios/smbios.h | 44 +++++++++++++++++++++++
>>>  2 files changed, 134 insertions(+)
>>>
>>> diff --git a/hw/smbios/smbios.c b/hw/smbios/smbios.c
>>> index 74c7102..6293bc5 100644
>>> --- a/hw/smbios/smbios.c
>>> +++ b/hw/smbios/smbios.c
>>> @@ -885,6 +885,9 @@ void smbios_entry_add(QemuOpts *opts)
>>>  {
>>>      const char *val;
>>>  
>>> +    int i, terminator_count = 2, table_str_field_count = 0;
>>> +    int *tables_str_field_offset = NULL;
>>> +
>>>      assert(!smbios_immutable);
>>>  
>>>      val = qemu_opt_get(opts, "file");
>>> @@ -926,7 +929,94 @@ void smbios_entry_add(QemuOpts *opts)
>>>              smbios_type4_count++;
>>>          }
>>>  
>>> +        switch (header->type) {
>>> +        case 0:
>>> +            tables_str_field_offset = g_malloc0(sizeof(int) * \
>>> +                                                TYPE_0_STR_FIELD_COUNT);
>>> +            tables_str_field_offset = (int []){\
>>> +                                    TYPE_0_STR_FIELD_OFFSET_VENDOR, \
>>> +                                    TYPE_0_STR_FIELD_OFFSET_BIOS_VERSION, \
>>> +                                    
>>> TYPE_0_STR_FIELD_OFFSET_BIOS_RELEASE_DATE};
> 
> This assignment doesn't do what you think it does.
> "tables_str_field_offset" is a pointer, and the result of the
> 
>   (int []){...}
> 
> compound literal is an unnamed array object with automatic storage
> duration. The lifetime of the unnamed array object is limited to the
> scope of the enclosing block, which means the "switch" statement here.
> 
> The assignment does not copy the contents of the array into the
> dynamically allocated area; instead, the unnamed array object is
> converted to a pointer to its first element, and the
> "tables_str_field_offset" pointer is set to that value. The original
> dynamic allocation is leaked.
> 
>>> +            table_str_field_count = sizeof(tables_str_field_offset) / \
>>> +                                    sizeof(tables_str_field_offset[0]);
> 
> This is wrong again; the dividend is the size of the pointer, not that
> of the pointed-to-array. The size of the array is not available through
> the pointer.
> 
> I assume testing has been done with 64-bit builds, so that the pointer
> size is 8 bytes, and the pointed-to type is 4 bytes ("int"). This would
> yield a count of 2, and I guess no input was tested where only the third
> (or a later) string pointer was nonzero.
> 
>>> +            break;
>>> +        case 1:
>>> +            tables_str_field_offset = g_malloc0(sizeof(int) * \
>>> +                                                TYPE_1_STR_FIELD_COUNT);
>>> +            tables_str_field_offset = (int []){
>>> +                                    TYPE_1_STR_FIELD_OFFSET_MANUFACTURER, \
>>> +                                    TYPE_1_STR_FIELD_OFFSET_PRODUCT, \
>>> +                                    TYPE_1_STR_FIELD_OFFSET_VERSION, \
>>> +                                    TYPE_1_STR_FIELD_OFFSET_SERIAL, \
>>> +                                    TYPE_1_STR_FIELD_OFFSET_SKU, \
>>> +                                    TYPE_1_STR_FIELD_OFFSET_FAMILY};
>>> +            table_str_field_count = sizeof(tables_str_field_offset) / \
>>> +                                    sizeof(tables_str_field_offset[0]);
>>> +            break;
>>> +        case 2:
>>> +            tables_str_field_offset = g_malloc0(sizeof(int) * \
>>> +                                                TYPE_2_STR_FIELD_COUNT);
>>> +            tables_str_field_offset = (int []){\
>>> +                                    TYPE_2_STR_FIELD_OFFSET_MANUFACTURER, \
>>> +                                    TYPE_2_STR_FIELD_OFFSET_PRODUCT, \
>>> +                                    TYPE_2_STR_FIELD_OFFSET_VERSION, \
>>> +                                    TYPE_2_STR_FIELD_OFFSET_SERIAL, \
>>> +                                    TYPE_2_STR_FIELD_OFFSET_ASSET, \
>>> +                                    TYPE_2_STR_FIELD_OFFSET_LOCATION};
>>> +            table_str_field_count = sizeof(tables_str_field_offset) / \
>>> +                                    sizeof(tables_str_field_offset[0]);
>>> +            break;
>>> +        case 3:
>>> +            tables_str_field_offset = g_malloc0(sizeof(int) * \
>>> +                                                TYPE_3_STR_FIELD_COUNT);
>>> +            tables_str_field_offset = (int []){\
>>> +                                    TYPE_3_STR_FIELD_OFFSET_MANUFACTURER, \
>>> +                                    TYPE_3_STR_FIELD_OFFSET_VERSION, \
>>> +                                    TYPE_3_STR_FIELD_OFFSET_SERIAL, \
>>> +                                    TYPE_3_STR_FIELD_OFFSET_ASSET, \
>>> +                                    TYPE_3_STR_FIELD_OFFSET_SKU};
>>> +            table_str_field_count = sizeof(tables_str_field_offset) / \
>>> +                                    sizeof(tables_str_field_offset[0]);
>>> +            break;
>>> +        case 4:
>>> +            tables_str_field_offset = g_malloc0(sizeof(int) * \
>>> +                                                TYPE_4_STR_FIELD_COUNT);
>>> +            tables_str_field_offset = (int []){\
>>> +                                    TYPE_4_STR_FIELD_OFFSET_SOCKET, \
>>> +                                    
>>> TYPE_4_STR_FIELD_OFFSET_PROCESSOR_MANUFACTURER, \
>>> +                                    
>>> TYPE_4_STR_FIELD_OFFSET_PROCESSOR_VERSION, \
>>> +                                    TYPE_4_STR_FIELD_OFFSET_SERIAL, \
>>> +                                    TYPE_4_STR_FIELD_OFFSET_ASSET, \
>>> +                                    TYPE_4_STR_FIELD_OFFSET_PART};
>>> +            table_str_field_count = sizeof(tables_str_field_offset) / \
>>> +                                    sizeof(tables_str_field_offset[0]);
>>> +            break;
>>> +        case 17:
>>> +            tables_str_field_offset = g_malloc0(sizeof(int) * \
>>> +                                                TYPE_17_STR_FIELD_COUNT);
>>> +            tables_str_field_offset = (int []){\
>>> +                                    
>>> TYPE_17_STR_FIELD_OFFSET_DEVICE_LOCATOR, \
>>> +                                    TYPE_17_STR_FIELD_OFFSET_BANK_LOCATOR, 
>>> \
>>> +                                    TYPE_17_STR_FIELD_OFFSET_MANUFACTURER, 
>>> \
>>> +                                    TYPE_17_STR_FIELD_OFFSET_SERIAL, \
>>> +                                    TYPE_17_STR_FIELD_OFFSET_ASSET, \
>>> +                                    TYPE_17_STR_FIELD_OFFSET_PART};
>>> +            table_str_field_count = sizeof(tables_str_field_offset) / \
>>> +                                    sizeof(tables_str_field_offset[0]);
>>> +            break;
>>> +        default:
>>> +            break;
>>> +        }
> 
> So, at this point, with the switch statement's scope terminated,
> "tables_str_field_offset" points into released storage.
> 
>>> +
>>> +        for (i = 0; i < table_str_field_count; i++) {
>>> +            if (*(uint8_t *)(smbios_tables + tables_str_field_offset[i]) > 
>>> 0) {
>>> +                terminator_count = 1;
>>> +                break;
>>> +            }
>>> +        }
>>> +
> 
> The condition can be rewritten more simply as follows (because
> smbios_tables already has type (uint8_t*):
> 
>   smbios_tables[tables_str_field_offset[i]] > 0
> 
> Independently of the bug that "tables_str_field_offset" points into
> released storage, the above expression is unsafe in its own right.
> Namely, we are not checking whether
> 
>   tables_str_field_offset[i] < smbios_tables_len
> 
> (And even if we wanted to enforce that, the "smbios_tables_len" variable
> is incremented only below:)

Another bug I failed to notice at first: we are checking offsets from
the beginning of the entire "smbios_table" array. That's not good when
we already have at least one SMBIOS table in that array; we should be
checking the last table that we just read from the file and appended to
"smbios_tables". Therefore the offset should be

    smbios_tables_len + tables_str_field_offset[i]

I assume this issue was not noticed because only one table was passed in
for testing.

Anyway, I'm not suggesting to fix these problems; I'm just pointing them
out for completeness.

My proposal is to extend dmidecode.

Honestly, I don't even understand why dmidecode doesn't have this
capability yet: dump precisely one table (that is, instance N of table
type K) as it is in memory, defined by the SMBIOS spec. The SMBIOS spec
says in 6.1.1 "Structure evolution and usage guidelines",

    Each structure shall be terminated by a double-null (0000h), either
    directly following the formatted area (if no strings are present)
    or directly following the last string. This includes system- and
    OEM-specific structures and allows upper-level software to easily
    traverse the structure table. (See structure-termination examples
    later in this clause.)

In other words, the terminator is part of the table.

Thanks
Laszlo

> 
>>>          smbios_tables_len += size;
>>> +        smbios_tables_len += terminator_count;
>>>          if (size > smbios_table_max) {
>>>              smbios_table_max = size;
>>>          }
> 
> Wrong again: we haven't allocated additional storage for the
> terminator(s). We've allocated extra space that's enough only for the
> loaded file itself:
> 
>         size = get_image_size(val);
>         if (size == -1 || size < sizeof(struct smbios_structure_header)) {
>             error_report("Cannot read SMBIOS file %s", val);
>             exit(1);
>         }
> 
>         /*
>          * NOTE: standard double '\0' terminator expected, per smbios spec.
>          * (except in legacy mode, where the second '\0' is implicit and
>          *  will be inserted by the BIOS).
>          */
>         smbios_tables = g_realloc(smbios_tables, smbios_tables_len + size);
>         header = (struct smbios_structure_header *)(smbios_tables +
>                                                     smbios_tables_len);
> 
> (In fact, the comment spells out the requirement for the external files
> provided by the user.
> 
>>> diff --git a/include/hw/smbios/smbios.h b/include/hw/smbios/smbios.h
>>> index 1cd53cc..6d59c3d 100644
>>> --- a/include/hw/smbios/smbios.h
>>> +++ b/include/hw/smbios/smbios.h
>>> @@ -267,4 +267,48 @@ void smbios_get_tables(const struct 
>>> smbios_phys_mem_area *mem_array,
>>>                         const unsigned int mem_array_size,
>>>                         uint8_t **tables, size_t *tables_len,
>>>                         uint8_t **anchor, size_t *anchor_len);
>>> +
>>> +#define TYPE_0_STR_FIELD_OFFSET_VENDOR 0x4
>>> +#define TYPE_0_STR_FIELD_OFFSET_BIOS_VERSION 0x5
>>> +#define TYPE_0_STR_FIELD_OFFSET_BIOS_RELEASE_DATE 0x8
>>> +#define TYPE_0_STR_FIELD_COUNT 3
>>> +
>>> +#define TYPE_1_STR_FIELD_OFFSET_MANUFACTURER 0x4
>>> +#define TYPE_1_STR_FIELD_OFFSET_PRODUCT 0x5
>>> +#define TYPE_1_STR_FIELD_OFFSET_VERSION 0x6
>>> +#define TYPE_1_STR_FIELD_OFFSET_SERIAL 0x7
>>> +#define TYPE_1_STR_FIELD_OFFSET_SKU 0x19
>>> +#define TYPE_1_STR_FIELD_OFFSET_FAMILY 0x1a
>>> +#define TYPE_1_STR_FIELD_COUNT 6
>>> +
>>> +#define TYPE_2_STR_FIELD_OFFSET_MANUFACTURER 0x4
>>> +#define TYPE_2_STR_FIELD_OFFSET_PRODUCT 0x5
>>> +#define TYPE_2_STR_FIELD_OFFSET_VERSION 0x6
>>> +#define TYPE_2_STR_FIELD_OFFSET_SERIAL 0x7
>>> +#define TYPE_2_STR_FIELD_OFFSET_ASSET 0x8
>>> +#define TYPE_2_STR_FIELD_OFFSET_LOCATION 0xa
>>> +#define TYPE_2_STR_FIELD_COUNT 6
>>> +
>>> +#define TYPE_3_STR_FIELD_OFFSET_MANUFACTURER 0x4
>>> +#define TYPE_3_STR_FIELD_OFFSET_VERSION 0x6
>>> +#define TYPE_3_STR_FIELD_OFFSET_SERIAL 0x7
>>> +#define TYPE_3_STR_FIELD_OFFSET_ASSET 0x8
>>> +#define TYPE_3_STR_FIELD_OFFSET_SKU 0x14
>>> +#define TYPE_3_STR_FIELD_COUNT 5
>>> +
>>> +#define TYPE_4_STR_FIELD_OFFSET_SOCKET 0x4
>>> +#define TYPE_4_STR_FIELD_OFFSET_PROCESSOR_MANUFACTURER 0x7
>>> +#define TYPE_4_STR_FIELD_OFFSET_PROCESSOR_VERSION 0x10
>>> +#define TYPE_4_STR_FIELD_OFFSET_SERIAL 0x20
>>> +#define TYPE_4_STR_FIELD_OFFSET_ASSET 0x21
>>> +#define TYPE_4_STR_FIELD_OFFSET_PART 0x22
>>> +#define TYPE_4_STR_FIELD_COUNT 6
>>> +
>>> +#define TYPE_17_STR_FIELD_OFFSET_DEVICE_LOCATOR 0x10
>>> +#define TYPE_17_STR_FIELD_OFFSET_BANK_LOCATOR 0x11
>>> +#define TYPE_17_STR_FIELD_OFFSET_MANUFACTURER 0x17
>>> +#define TYPE_17_STR_FIELD_OFFSET_SERIAL 0x18
>>> +#define TYPE_17_STR_FIELD_OFFSET_ASSET 0x19
>>> +#define TYPE_17_STR_FIELD_OFFSET_PART 0x1a
>>> +#define TYPE_17_STR_FIELD_COUNT 6
>>>  #endif /* QEMU_SMBIOS_H */
>>> -- 
>>> 2.9.2
> 
> This hunk demonstrates why, in my opinion, this approach doesn't scale.
> This way QEMU should know about every offset in every table type. If a
> new version of the SMBIOS spec were released, QEMU would have to learn
> the internals of the new tables.
> 
> I think this is the wrong approach. "dmidecode" is the dedicated tool
> for working with SMBIOS tables. Whenever a new spec version is released,
> dmidecode is unconditionally updated. We should try to teach dmidecode
> to output directly loadable SMBIOS tables. QEMU is an important enough
> project to exert this kind of influence on dmidecode.
> 
> (Thanks for the CC, Michael!)
> 
> Thanks
> Laszlo
> 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]