Frank Hofmann's Weblog Frank Hofmann's Weblog

Monday Jun 20, 2005

All the time when I wrote my blog entry about how Solaris/x86 "fakes" hostIDs, I had this deja-vu feeling of sorts. I finally found what this story reminded me of - this old DrFun cartoon.

Visionary :)

Saturday Jun 18, 2005

Introducing myself

Being new to the blogging community, I guess it's a polite thing to say a few words about myself before I overwhelm you with that stream of thoughts of mine.

As so many of my "learned" profession (I've graduated in Physics back in '97), my path lead me away from research and into computing. Having done system programming and administration on a HP/UX monoculture in the institute of physics for several years, I shunned that HP offer and joined Sun instead (hey, even after all these years of Sun not exactly doing well I'm still happy about this decision)...
So from Sun Service to Solaris Sustaining I went over the next years, and I'm still there - bugfixing Solaris, as my job description says I should spend around 80% of my time on.
The big advantage of the Sustaining Engineering role is that you come to see a lot of different places in the Solaris Operating Environment over time. So I've worked on the SPARC kernel, various filesystems, threading, VM, even OBP, and since we turned our focus back onto x86 also on the x86/x64 kernel. If it weren't for the urgency-to-patch of escalated issues that now and then interrupt the cosy "what bug can I fix today" state, life would be paradise ...

There is a life outside of work; as far as that goes, I'm first and formost into astronomy.
I've started stargazing as a kid - the first book I ever owned was titled Eine Reise zu den Sternen (A journey to the stars), consisting of little stories where grandpa explained the constellations to his grandson over the course of a year, one at a time and always directing to the next based on those having been shown previously. A wonderful book, it kept me enthralled for a few months. I keep it to this day, and no it's not for sale. Like none of the other Astro-gear that I own :)

I do like to travel and see foreign places. And I like good food & wine, if you happen to be German, Wine-Lover and in the Farnborough/UK area, contact me about having a bottle together.

But now, on to more interesting things !

The dark side of the source - hostids

There are far too many software vendors who love proprietary lock-in, hardware lock-in, or better a combination of both (Think: XP activation). This is usually called Trusted Computing, in the clear sense of "we trust you to pay us big $$$ if we give you no choice".

A hardware ID and software querying for this is usually the mechanism to implement this. The UNIX specifications actually contain such a query mechanism - see gethostid. Comittees are very careful about committing to anything, and therefore the OpenGroup's manpage clearly says "The Open Group does not define the domain in which the return value is unique"...
Now since we have OpenSolaris, hostid generation doesn't need to remain secret anymore. Let's therefore do a little tour of the OpenSolaris sourcecode and identify how the hostid is generated. I'll start with the x86/x64 platforms, but might eventually come to talk about OBP and SPARC and how even the OBP .hostid command there can be fooled into reporting something fancy like deaff001, as a reminder to those that believe closedness to be a feature ...
A quick search on the OpenSolaris CVS gives us the sourcecode for gethostid() which is extremely simple:


usr/src/lib/libc/port/gen/gethostid.c
44 long
45 gethostid(void)
46 {
47 	char	name[HOSTIDLEN+1], *end;
48 	unsigned long	hostid;
49 
50 	if (sysinfo(SI_HW_SERIAL, name, HOSTIDLEN) == -1)
51 		return (-1);
52 	hostid = strtoul(name, &end, 10);
53 	if (end == name)
54 		return (-1);
55 	return ((long)hostid);
56 }
57

So this performs a call to a Solaris system call sysinfo. There's more about how system calls are dispatched/defined here, here, and here, so I needn't go into the implementation details of the "glue code" at this place again.
Let's jump straight ahead to the kernel.


usr/src/uts/common/syscall/systeminfo.c
 54 long
 55 systeminfo(int command, char *buf, long count)
 56 {
 57 	int error = 0;
 58 	long strcnt, getcnt;
 59 	char *kstr;
 60
[ ... ]
105 	case SI_HW_SERIAL:
106 		kstr = hw_serial;
107 		break;
[ ... ]
125 	if (kstr != NULL) {
[ ... ]
132 		if (copyout(kstr, buf, getcnt))
133 			return (set_errno(EFAULT));
134 		return (strcnt + 1);
135 	}

So this just takes that string from a kernel global hw_serial. Searching the sources for the definition gives us the first (but by far not the last) attempt at obfuscation:


usr/src/uts/common/conf/param.c
496 /*
497  * On x86 machines, read hw_serial, hw_provider and srpc_domain from
498  * /etc/bootrc at boot time.
499  */
500 char architecture[] = "i386";
501 char architecture_32[] = "i386";
502 char hw_serial[11] = "0";
503 char hw_provider[SYS_NMLN] = "";

Don't always trust comments to reflect what the code actually does. Some may talk about how things were in a distant past, others may be wishful thinking - use the source, Luke, and check what's really going on.
At this point, I'll have to put the SPARC folks off to some later time; sorry guys, you're not forgotten and exploring the bowels of Forth and OBP surely is interesting, but my time is limited and blogs.sun.com isn't the newsletter of WhatTheHack (hmm, on second thought ...)
As far as the x86/x64 platforms are concerned, searching the source for hw_serial finds us no place where this is assigned a value to. But instead we find:


usr/src/uts/i86pc/os/startup.c
1336 	/*
1337 	 * This is needed here to initialize hw_serial[] for cluster booting.
1338 	 */
1339 	if ((i = modload("misc", "sysinit")) != (unsigned int)-1)
[ ... ]
1855 extern char hw_serial[];
1856 char *_hs1107 = hw_serial;

Hmm - somebody doesn't like descriptive variable names. And honestly, of course, _hs1107 sounds much more trustworthy than a plain "hw_serial". Anyway, we're now right at where we want to be - a sourcefile without a single comment in it:


usr/src/uts/common/io/sysinit.c
35 #define	V1	0x38d4419a
36 #define	V1_K1	0x7a5fd043
37 #define	V1_K2	0x65cb612e
38 
39 static int32_t t[3] = { V1, V1_K1, V1_K2 };
40 
41 extern ulong_t  _bdhs34;
42 extern char    *_hs1107;
43 
44 #define	A	16807
45 #define	M	2147483647
46 #define	Q	127773
47 #define	R	2836
48 
49 #define	x() if ((s = ((A*(s%Q)) - (R*(s/Q)))) <= 0) s += M
50 
51 void
52 sysinit(void)
53 {
54 	char *cp;
55 	char d[10];
56 	int32_t s, v;
57 	int i;
58 
59 	s = t[1];
60 	x();
61 	if (t[2] == s) {
62 		x();
63 		s %= 1000000000;
64 	}
65 	else
66 		s = 0;
67 
68 	for (v = s, i = 0; i < 10; i++) {
69 		d[i] = v % 10;
70 		v /= 10;
71 		if (v == 0)
72 			break;
73 	}
74 	for (cp = _hs1107; i >= 0; i--)
75 		*cp++ = d[i] + '0';
76 	*cp = 0;
77 	_bdhs34 = (ulong_t)s + (ulong_t)&_bdhs34;
78 } 

Nice and obfuscated. But all that this "driver" contains. With the source open, one could simply put a hostID of your choice into hw_serial, but then, as indicated, the whole point of the original implementation seems to have been security by obscurity - never a good idea.
So, the details of this aside: This shows the hostID is simply muched together from two out of the three 32bit values stored in the array t[3] from the kernel module /kernel/misc/sysinfo.

But this isn't system-specific ! It doesn't attempt to query any hardware-specific information to determine the value.
Then why on earth do you get a system-specific hostid out of this ? There's more obfuscation around - but again none that the source wouldn't tell us about ...


usr/src/uts/intel/sysinit/Makefile
54 #
55 #	Avoid signing the sysinit modules
56 #	    They will be modified during installation,
57 #	    rendering any signature invalid.
58 #

Aha, so that's it - "modified during installation". The Solaris install process simply patches the binary - and it really patches up only the bare minimum - t[1] and t[2]. Compile a sysinit module and compare it with the one installed on your Solaris/x86 or Solaris/x64 system:

$ pwd
/share/bld/u/frankho/onnv-lowcarbfs/usr/src/uts/intel/sysinit/obj64
$ od -A x -t x4 sysinit | grep 38d4419a
00002c0 38d4419a 7a5fd043 65cb612e 00000000
$ od -A x -t x4 /kernel/misc/amd64/sysinit | grep 38d4419a
00002c0 38d4419a 571ad928 23a6fdc5 00000000

So the install process just patches up t[1] and t[2] to end up with something that's "hostspecific".

And even that's not true - just reinstall the same machine from scratch, and you'll get a different host ID. I cannot show you the source for the install program since the Solaris installation utilities are not yet part of OpenSolaris, but for all practical purposes it's safe to assume that the install process simply assigns a random number to be the hostid !
Hmm - opens up the final question that I can answer from the OpenSolaris sources: How can I upgrade a Solaris/x86 system then without loosing the hostID ? The answer to that is so simple that I needn't even show it - upgrade preserves the old kernel/misc/sysinfo file, and patches up the new one so that the hostID will stay the same. We've even made sure that BFU won't clobber the hostid:


usr/src/tools/scripts/bfu.sh
336 #
337 # files to be preserved, ie unconditionally restored to "child" versions
338 #
339 preserve_files="
340 	kernel/misc/sysinit

So what do we learn out of all this:

  1. Solaris/x86 hostIDs are not derived from actual "hardware IDs".
  2. Solaris/x86 hostIDs can be modified by changing /kernel/misc/sysinit.
  3. Cloning disk as means of installing Solaris/x86 isn't a good idea if you want to run software that relies on machine-specific hostIDs.

SPARC, on the other hand, is a little bit of a different story. I'll tell it eventually, I promise. And yes, I'm bribeable with good German Riesling wine ...

OpenSolaris Solaris