From: Laszlo Ersek <lersek@redhat.com>
To: "Michael S. Tsirkin" <mst@redhat.com>, Lin Ma <lma@suse.com>
Cc: imammedo@redhat.com, qemu-devel@nongnu.org, famz@redhat.com
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 [thread overview]
Message-ID: <132fd028-d4da-5b6a-eeea-a6401b5da7e5@redhat.com> (raw)
In-Reply-To: <c3aeb863-6e0c-d3a0-e21b-0def4b2bb16b@redhat.com>
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 <lma@suse.com>
>>
>> Seems to make sense superficially
>>
>> Acked-by: Michael S. Tsirkin <mst@redhat.com>
>>
>> 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
>
prev parent reply other threads:[~2016-11-10 17:31 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-09-06 8:28 [Qemu-devel] [PATCH] smbios: Add 1 terminator if there is any string field defined in given table Lin Ma
2016-11-07 16:25 ` [Qemu-devel] 答复: " Lin Ma
2016-11-07 16:31 ` Daniel P. Berrange
2016-11-07 16:45 ` Lin Ma
2016-11-10 15:09 ` [Qemu-devel] " Michael S. Tsirkin
2016-11-10 17:18 ` Laszlo Ersek
2016-11-10 17:31 ` Laszlo Ersek [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=132fd028-d4da-5b6a-eeea-a6401b5da7e5@redhat.com \
--to=lersek@redhat.com \
--cc=famz@redhat.com \
--cc=imammedo@redhat.com \
--cc=lma@suse.com \
--cc=mst@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).