Showing posts with label UUID. Show all posts
Showing posts with label UUID. Show all posts

09 June 2012

SMBIOS UUID FAIL

Abstract

Somewhere on the system board of virtually every Wintel computer in existence there is a programatically accessible identifier (a 128-bit number). In theory, this identifier is unique to each system board and may be used to differentiate this particular computer from every other computer. In practice, you cannot rely on this being the case and therefore this identifier is useless.

Detail

In the SMBIOS System Information table (Type 1) there is a field called UUID which is described as "Universal Unique ID number. If the value is all FFh, the ID is not currently present in the system, but is settable. If the value is all 00h, the ID is not present in the system."

So let's assume your program reads this value (either directly or via WMI Win32_ComputerSystemProduct, for example) and the value is not all FFh or all 00h; what two properties would you expect of this universally unique identifier? I suggest you'd want it to be (a) a unique value, and (b) an unchanging value.

Oh dear.

I've probably seen more SMBIOS UUIDs than you've had hot dinners, and I can tell you that the UUID is not always unique and not always constant.

Sometimes the UUID has some kind of "obviously" non-unique place-holder, such as a repeated pattern. For example, it might be 58585858585858585858585858585858 (58 hex = ASCII 'X') or it might be 00020003000400050006000700080009. (And don't forget that, although it looks like a pretty obvious pattern, when read in little-endian byte order, as the SMBIOS UUID field should be (another sorry tale of woe), the UUID you will get is {03000200-0400-0500-0006-000700080009}.) You might write some code that could detect some of the more obvious patterns and discard the UUID as probably non-unique when you find them. By the way, both of these examples are from real computers.

Sometimes there is no discernable pattern in the UUID - it looks random. That is good. Except when you find that somehow every computer at that customer site has exactly the same "random" UUID.

Again, sometimes the UUID looks truly random, and no two computers appear to have the same UUID. And then you restart the computer and now you have a new UUID, completely different to the one the computer had last time you looked! This is pretty rare, but I've seen it happen. A more common problem is that, for some reason, someone in the organisation changed the UUID, maybe deliberately, maybe unintentionally.

I think that for the majority of computers the UUID is a unique constant. But because some computers have bad UUIDs, and because you cannot always tell the good from the bad just by looking at the UUID, the only safe thing to do is assume that all computers have bad UUIDs. And that's a shame. I guess a few people spoiled it for everyone.

Perhaps I misunderstood the SMBIOS documentation. To be fair, it doesn't actually say "if present, this value will be unique to this system board and will never change." It's rather terse on the subject. But it does say, "Universal Unique ID number." So, you know, I assumed the ID would be unique. Perhaps they meant to say, "Potentially Universal Unique ID number. Look, we are just providing somewhere to put a 128-bit number, and if someone sets it to a unique value, and they don't change it, then it will be unique and constant."

It occurs to me that someone might attempt to defend the SMBIOS UUID by saying that it is the responsibility of computer manufacturers to ensure that this field is correctly set, and if the value isn't unique the issue should be taken up with them. I don't mean to be rude, but get real! Imagine you are a little independent software company and you are fortunate to sell your product to a multinational company. They roll it out across the 14 countries in which they operate, onto their 50,000 computers of 57 different brands. There are problems with your product because you were foolish enough to rely on the SMBIOS UUID being a unique constant. But don't worry, just tell MegaCorp to take it up with their computer suppliers. "Right oh, will do" will not be the cheery response from MegaCorp. (This is a purely hypothetical example.)

My advice is, by all means obtain the SMBIOS UUID and show it to the user, but don't write code that relies on the value being unique and constant, because it may not be either.

index of blog posts

25 March 2009

bytefluo

I was reading Gulliver’s Travels to one of my children tonight and we came across the term Big-endian in a passage about the reason for the war between Lilliputians and Blefuscudians (the latter crack their eggs at the big end, the former at the little end). Although I read Gulliver’s Travels many years ago I had obviously forgotten this because I assumed that big- and little-endian were just computer jargon. But it seems Swift coined the term.

It is computed that eleven thousand persons have at several times suffered death, rather than submit to break their eggs at the smaller end. Many hundred large volumes have been published upon this controversy: but the books of the Big-endians have been long forbidden, and the whole party rendered incapable by law of holding employments. During the course of these troubles, the emperors of Blefusca did frequently expostulate by their ambassadors, accusing us of making a schism in religion, by offending against a fundamental doctrine of our great prophet Lustrog, in the fifty-fourth chapter of the Blundecral (which is their Alcoran). This, however, is thought to be a mere strain upon the text; for the words are these: 'that all true believers break their eggs at the convenient end.'

Gulliver’s Travels, Chapter 4

Anyway, following my previous post about UUID and Byte Order I wanted to advertise a way to break your eggs at the convenient end, namely a C++ class called bytefluo. Here’s how you might use it to read big- and little-endian UUIDs




#include "bytefluo.h"

namespace RFC_4122 {
// types borrowed from RFC 4122

typedef unsigned long unsigned32;
typedef unsigned short unsigned16;
typedef unsigned char unsigned8;
typedef unsigned char byte;

typedef struct {
unsigned32 time_low;
unsigned16 time_mid;
unsigned16 time_hi_and_version;
unsigned8 clock_seq_hi_and_reserved;
unsigned8 clock_seq_low;
byte node[6];
} uuid_t;
}//namespace RFC_4122

namespace {
// read UUID structure from given bytefluo stream
RFC_4122::uuid_t read_common(bytefluo & buf)
{
// note that this code will read the data in the correct byte order
// regardless of the natural byte order of this system; also reading
// the structure one field at a time makes it immune to possible
// compiler-specific structure packing for machine word alignment
RFC_4122::uuid_t result;
buf >> result.time_low
>> result.time_mid
>> result.time_hi_and_version
>> result.clock_seq_hi_and_reserved
>> result.clock_seq_low;
buf.read(result.node, sizeof(result.node));
return result;
}
}//anonymous namespace

// read big-endian encoded UUID structure from given 16-byte buffer
RFC_4122::uuid_t uuid_from_16_bytes_big_endian(const unsigned char * bytes)
{
bytefluo buf(bytes, bytes + 16, bytefluo::big_endian);
return read_common(buf);
}

// read little-endian encoded UUID structure from given 16-byte buffer
RFC_4122::uuid_t uuid_from_16_bytes_little_endian(const unsigned char * bytes)
{
bytefluo buf(bytes, bytes + 16, bytefluo::little_endian);
return read_common(buf);
}

int main()
{
const unsigned char bytes[16] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};
using RFC_4122::uuid_t;

// first read raw data assuming big-endian byte ordering
{
const uuid_t uuid(uuid_from_16_bytes_big_endian(bytes));
assert(uuid.time_low == 0x00112233);
assert(uuid.time_mid == 0x4455);
assert(uuid.time_hi_and_version == 0x6677);
assert(uuid.clock_seq_hi_and_reserved == 0x88);
assert(uuid.clock_seq_low == 0x99);
assert(::memcmp(uuid.node, "\xAA\xBB\xCC\xDD\xEE\xFF", 6) == 0);
}

// now read the same raw data assuming little-endian byte ordering
{
const uuid_t uuid(uuid_from_16_bytes_little_endian(bytes));
assert(uuid.time_low == 0x33221100);
assert(uuid.time_mid == 0x5544);
assert(uuid.time_hi_and_version == 0x7766);
assert(uuid.clock_seq_hi_and_reserved == 0x88);
assert(uuid.clock_seq_low == 0x99);
assert(::memcmp(uuid.node, "\xAA\xBB\xCC\xDD\xEE\xFF", 6) == 0);
}
}

Notice how bytefluo abstracts away the details of reading big- and little-endian scalars? Get your copy here!


 

index of blog posts

13 March 2009

UUID and Byte Order

Say you have a UUID stored in memory as an array of 16 contiguous bytes like this

const unsigned char bytes[16] = {
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};

And say you need a function that can take a pointer to the first byte of such an array and return the equivalent curly-bracketed string representation of that UUID

std::string uuid_string(const unsigned char *);

You know the function should return a string something like this
    {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}

But do you know precisely what the string should be for the array given above?

UUIDs are documented in RFC 4122. Here is an extract from that RFC:
In the absence of explicit application or presentation protocol
specification to the contrary, a UUID is encoded as a 128-bit object,
as follows:

The fields are encoded as 16 octets, with the sizes and order of the
fields defined above, and with each field encoded with the Most
Significant Byte first (known as network byte order).  Note that the
field names, particularly for multiplexed fields, follow historical
practice.

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          time_low                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       time_mid                |         time_hi_and_version   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|clk_seq_hi_res |  clk_seq_low  |         node (0-1)            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         node (2-5)                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

So, assuming our 16-byte array is encoded in the format recommended in RFC 4122, each field will be stored most-significant byte first and our function should return the string
    {00112233-4455-6677-8899-AABBCCDDEEFF}

However...

“In the absence of explicit application or presentation protocol specification to the contrary...”, so in other words you cannot write a universally applicable 16-byte array to UUID string function because the encoding of the UUID has not been mandated by the RFC.

RFC 4122 may be perfectly clear, but I believe the freedom it permits could cause confusion. Here is an example where the default encoding described in RFC 4122 was not adopted:
Although RFC 4122 recommends network byte order for all fields, the PC industry (including the ACPI, UEFI, and Microsoft specifications) has consistently used little-endian byte encoding for the first three fields: time_low, time_mid, time_hi_and_version. The same encoding, also known as wire format, should also be used for the SMBIOS representation of the UUID. System Management BIOS (SMBIOS) Specification v2.6

But the UUID field was introduced in SMBIOS specification version 2.1 and the above quoted clarification was absent from that specification; one was just told “UUID; 16 BYTEs” (and something about what all FFs and all 00s meant) and left to get on with it. And you would have been wrong to read those 16 bytes assuming they were big-endian. Had you done so you would at some point have found that your UUID for a given computer did not match the UUID someone else obtained for that same computer via some other means, say WMI. And you might have felt both annoyed and embarrassed about it; embarrassed that you didn’t make more effort to check that your interpretation was correct in the first place and annoyed that the documentation was not more explicit - so annoyed that you’d feel the need to blog about how unfair it all was.

To summarise: if the encoding is big-endian, as described in RFC 4122, the UUID represented by the bytes
const unsigned char bytes[16] = {
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
    0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
};

will be
    {00112233-4455-6677-8899-AABBCCDDEEFF}

But if the encoding is little-endian, as described in SMBIOS V2.6, the same 16 bytes will represent the UUID
    {33221100-5544-7766-8899-AABBCCDDEEFF}

Perhaps your system uses some other UUID encoding...


UPDATE June 2012

When I read the note in the SMBIOS v2.6 specification I interpreted it not as saying the format was changing from big- to little-endian, but as saying that the UUID had always been stored little-endian, but this had hitherto not been explicitly stated. But now it has been explicitly stated that the value is stored little-endian what should you do if you've got code out in the field that's written assuming it was stored big-endian? I think these are your choices:

a) You could leave your code as it is. But that would mean your code may get a different value to that obtained through another means, such as WMI. This is what I described above. The upside is that your code is consistent, which may or may not matter in your application.

b) You could change your code so that it always reads the UUID in little-endian byte order. This is may make it more likely to be consistent with the value obtained through other means. But it does mean your code is not consistent, which may or may not matter in your application.

c) You could sometimes read it little-endian and sometimes big-endian, depending on the SMBIOS version number. For example, here is what is done in dmidecode:


    from dmidecode.c:
    /*
     * As off version 2.6 of the SMBIOS specification, the first 3
     * fields of the UUID are supposed to be encoded on little-endian.
     * The specification says that this is the defacto standard,
     * however I've seen systems following RFC 4122 instead and use
     * network byte order, so I am reluctant to apply the byte-swapping
     * for older versions.
     */
    if (ver >= 0x0206)
        printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
            p[3], p[2], p[1], p[0], p[5], p[4], p[7], p[6],
            p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
    else
        printf("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7],
            p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);


It doesn't seem right to me that the value of the SMBIOS UUID should depend on the programmer's judgement in this way.

Even after you get past this byte-order issue, there are other problems with the SMBIOS UUID you should be aware of.

index of blog posts