A Bug’s Life : Security Level Cray

Illustrations by Jonas Ekman.

When the nineties were getting old and people could value a pet shop at 100 million dollars as long as it had a web page, I was missing out on the hype by working as a sysadmin at a supercomputing site. This was before supercomputing got boring; when a super was a really odd machine running a demented OS instead of just being a thousand of what you have on your desktop. One of my babies was a Cray J90, more specifically a J932SE.

The J90 was, admittedly, not one of the awesomest Crays. It did not have an integrated couch (Cray 1, Cray XMP, Cray YMP).

Cray 1, when you want your super in the lobby.
Cray 1, when you want your super in the lobby.

You couldn’t serve beer on it (Cray 2).

The Cray 2 gets my vote as the coolest looking computer ever. It is a coolant aquarium!
The Cray 2 gets my vote as the greatest looking computer ever. It is a coolant aquarium!

It did not have a waterfall (Cray T90).

T916
A Cray T90 with coolant waterfall behind it.

However, it was a 32-CPU shared-memory vector supercomputer with a gigantic (at the time) 8GB of RAM. It was the size of 2 largish fridges, ran UNICOS — the Cray flavour of UNIX — and could multiply 64-bit floating point numbers like nobody’s business.

A Cray J90 wishing it had more awesome looks.
A Cray J90 wishing it had more awesome looks.

Seymour Cray was famous for his disdain for virtual memory and thus UNICOS did not have any. If you had 8GB of RAM then that was all you could use. When you wanted to start a process you’d better hope that the system could find a free, continuous chunk of RAM of the appropriate size. If your process ran out of memory it would be swapped out to disk in its entirety. Needless to say, swapping was death for performance.

A Cray machine was a reasonably expensive piece of equipment (some may even have called it unreasonably expensive) and most, very likely all, were bought to service many users at the same time. One of the system software packages Cray sold was called Multi-Level Security, MLS for short. MLS allowed you to virtually partition the machine in a fairly bullet-proof fashion. You could buy a 32-CPU system and let some users see 30 CPUs and half the memory while other users only saw 2 CPUs. If you were running in a partition you couldn’t access or even see processes running in another partition. In fact, you could be a system administrator and still have access to just a part of the physical machine. The effect was similar to virtualisation but without the performance penalty. MLS was a fabulous idea because it allowed, say, the NSA and the North Korean embassy to pool their resources and buy a Cray together and their users wouldn’t see each other. What could possibly go wrong?

MLS was a pig and a half and any sane sysadmin would run from it screaming. Some poor souls were forced to use it because it sounded like a great deal to management. We were lucky because our machine was open and we did not run MLS.

A typical sysadmin reaction.
A typical sysadmin reaction.

One carefree day I came into the office and found an envelope in my mail box with a DAT-tape in it. The label on the tape said UNICOS 10.0. UNICOS 10 had been out for a while and we could not put it off any longer, it was time to upgrade the OS on our machine from 9.0 to the latest and greatest.

An OS upgrade on a million dollar multi-user system is a pretty major undertaking. The machine ran batch jobs for dozens of users. Jobs could be queued for weeks waiting for a time slot to run in, and a single job could execute for days. Just turning the machine off would get you murdered by a PhD student (bad) or lectured at by some mechanics professor (worse). The OS upgrade had to be announced weeks in advance and planned like a Prussian wedding. The plan was written, the upgrade announced, the sacrificial goat was prepared and magical runes were appropriately carved on the computer room doors to ward off evil upgrade gremlins… the process for OS upgrades can be highly site-dependent.

The day arrived. We stopped the batch queues, rebooted the machine into single-user mode and went to work. The upgrade went well and after a few hours of testing we could restart the batch queue and let the users back on. Mission accomplished.

A week or so later I was doing some routine work (the technical term is “dicking around”) on the Cray and I discovered something strange: there was a file in the root directory that was owned by a normal user. The user was one of my co-workers and he did not have root access to the Cray. The file should have been unpossible. My immediate worry was that we had been hacked. A supercomputing site is a juicy target for hackers and a super, with its huge I/O capability and usually great network connectivity, can make an awesome denial-of-service booster.

The co-worker was trusted so I asked him about the file. He had been testing a script with a path bug in it so he could explain where the file came from. He could not explain how it had managed to be written in a read-only directory. This pretty much discounted the theory that the file was part of a hacking/cracking/fracking operation. Looking around some more I quickly found other files that were owned by users in places where those users didn’t have write access. After some testing the horrible truth dawned: any user could write anywhere. While academic institutions have a reputation for openness even Richard Stallman might agree that this was a step too far.

We had a common login system for all our machines. It was based on an implementation of Kerberos called Heimdal that had been developed locally and luckily for me I shared an office with one of the main authors of Heimdal. He was the kind of wizard every computing site needs. If you complained vaguely about some proprietary protocol not working he would sit quietly for 30 minutes (not acknowledging that he had even heard you) and then suddenly announce that he had reverse-engineered the protocol and mailed you the source code for a replacement server that he had coded up from scratch. Once, when confronted with what appeared to be a corrupt file name in a directory his first action was to hexdump the directory inode. While he did not have an actual, physical, beard, his virtual Unix beard was mighty to behold.

Everyone needs a UNIX beard.
Everyone needs a UNIX beard.

I was a bit out of my depth so I recruited my wizardly office mate to the cause. The best way to do that was to not ask for help directly but to just complain generally until he either got tired of listening to it or got curious. After applying beard to the problem for a while he managed to figure out what was going on.

The Heimdal kxd program was responsible for accepting logins via the network, authenticating them and spawning a user login session on the machine. When doing this it had to change the owner of the spawned process to the UID of the user logging in, which it did with the setuid() system call. This used to work just fine. But now the setuid() call changed the UID and left the process with root file access permissions.

As a part of the planning for the OS upgrade I had pored over the release notes for UNICOS 10 time and again to make sure that I hadn’t missed something that would cause us problems. One of the changes that Cray had made was that MLS, the Multi-Level Security that everyone loved so much, had been included as a default component in the OS. It used to be a separate product but now everyone got it. The release notes promised that if you disabled MLS in the kernel it would work exactly as a non-MLS system did in UNICOS 9. What the release note should have said was that if you disabled MLS it would work exactly as a non-MLS system did except for the part where we changed the way access permissions are inherited by the setuid() system call. A consensus was developing around the view that MLS was the spawn of satan.

Are you sure you didn't order a MLRS?
Are you sure you didn’t order a MLRS?

The virtually bearded one went quiet for 15 minutes or so and then announced that he had fixed it. The fix can still be found in the Heimdal source code today:

 if (setgid (passwd->pw_gid) ||
     initgroups(passwd->pw_name, passwd->pw_gid) ||
 #ifdef HAVE_GETUDBNAM /* XXX this happens on crays */
     setjob(passwd->pw_uid, 0) == -1 ||
 #endif
         setuid(passwd->pw_uid)) {
         syslog(LOG_ERR, "setting uid/groups: %m");
         fatal (kc, sock, "cannot set uid");
 }

I would frankly have been a lot less restrained in the comment…

Cleaning up the mess took a while, we had to check all file systems for integrity. Happily we had dumped all system disks to tape after the upgrade but prior to letting any users on so we had a known good set to compare against.

Lessons learned? If you change the semantics of setuid() then maybe, just maybe, drop a line about it in the release notes. Also, a paranoid mind set is a great asset to a system administrator (although it can definitely go off the rails without some healthy pushback from management) and preparation is key to any successful system upgrade.

While this MLS thing was a bit of a fiasco I have to say that UNICOS was incredibly stable. It just ran for years and years and the only crashes that I can remember were caused by hardware failures (typically hard drives). Oh, and that one time when I shorted out the entire machine with a carelessly applied screwdriver…