You maybe heard about the MacOS root password issue (i.e. #IAmRoot)? If you haven’t heard about it and you’re reading this on a Mac running High Sierra, go patch.  Seriously.  Go do it now (we’ll wait) — and also maybe bookmark this link in case you run into trouble with file sharing on the other side.

If you haven’t heard of it or you’re reading this on something other than High Sierra, you might want to check out the deep dive coverage of the issue via The Register.  They go down to the source code level to outline exactly what went wrong and why.  It makes for interesting reading and I promise you don’t need to have a working knowledge of C to follow along with the explanation (although it does help if you want to unpack the nitty gritty… which, by the way, would be a heck of a lot easier to read I’m sure if we had access to the constant declarations and variable names.  But we don’t, so think of it as a refresher for your C skills.)

This bug is really, really scary to me.  No, not because it sets the root password to a blank string.  Linux had one back in the day (2003) that set the uid to root (zero) in wait4 based on confusion about the assignment operator (=) vs. the comparison operator (==).  That Linux one was potentially worse than this because it could have potentially been (Ed Felten over at Princeton says it almost certainly was) an active attempt at subterfuge rather than an innocuous coding issue like #IAmRoot seems to be.  That’s not the scary part.  The scary part is something else which I’ll get to that in a minute, but a quick synopsis on what #IAmRoot actually is.

This bug essentially works this way:  you know that root is disabled on High Sierra, right? Or at least it’s supposed to be?  Well, since it’s disabled, it doesn’t have an entry in the password list.  So the shadow entry (which I’ll confess I was surprised to learn this isn’t /etc/shadow on MacOS — you learn something new every day) doesn’t exist.  If you try to log in with root, the system will check the password, which fails (appropriately).  Since the shadow validation failed, the system will check to see if there’s a corresponding legacy crypt() entry.  There isn’t of course, but the call to do that returns a non-zero value (i.e. i.e. “true”) [this is the bug part].  The system assumes, rightly based on my interpretation of how this call is supposed to work, that this means that there is in fact a crypt() password value and that what was supplied is, in fact, the right value.  Therefore, the system will attempt to “upgrade” the crypt() entry to a shadow password entry, copying the value (null) and creating a shadow password entry containing that value.  After that, null’s the new root password.

It’s not a complicated bug.  The part the scares the crap out of me though is that it made it to production.  No, seriously.  If I were Apple, and had oceans of developers and testers working on my product, I’d do pre-production testing for security issues – I’d probably have pre-release testing scripts to exercise security-relevant functionality.  You would too, right?  Were I to do so, I’d likely automate a number of commonly-occurring security-related events that might transpire once the product is in the field.  With me so far?  What would one of those test cases be?  Root login.  Probably a bunch of other stuff too, but almost certainly root login would be included.  It’s literally the least you can do.

In fact, I’d probably have an automated test that tries to log in with a number of different commonly-occurring root password values in addition to a “blank” value.  To what purpose you ask (since root should be disabled)?  A few reasons: 1) it takes like 5 minutes to script that and you can use that same script on every single release from now until passwords go away, 2) it costs you nothing to include a test scenario to do that (it takes what like 30 seconds to execute?), and 3) developers love to put in admin-level back doors and this would help stop that.  Here’s the real deal though: root login is supposed to be disabled, so you testing to make sure that it is is absolutely the minimum reasonable test case to ensure that functionality works.  Could you test it some other way — like checking if the password file exists?  You could, but that doesn’t exercise the functionality.  Not exercising the functionality is “pants-soilingly” scary.

This is code that changes from time to time.  You know every time that Apple introduces some new logon mechanism – like fingerprint or facial recognition?  That has the potential to change this code.  It might not change it every time, but my point is that this code has developers in it from time to time.  Say, for example, one of those developers accidentally does the same thing the Linux backdoor did in 2003.

For example, they change a line like this one (tweaked to make my point):

int result = verify_password(var70, rax, var60, &var54, $var41);
if ( result == 1) { logon(); }

to

int result = verify_password(var70, rax, var60, &var54, $var41);
if (result = 1) { logon(); }

See the difference?  You might not notice that in a sea of surrounding code, right?  Yeah, me neither.  And I say that as a person who was, at one point, wicked good at that (team members once called me the “human lint” — as in the validation tool not the fabric residue).

The point is, you need to be checking this stuff.  Since Apple didn’t catch this bug, I can only conclude that they didn’t have this test case in place.  Since this is absolutely the most fundamental and trivial test case that you’d possibly conceive of, I can only conclude that they are doing no security functional testing at all.  As in zero, nada, zip, the null set.  That is really, really not a good sign – particularly for a product like this one that has such a large user base and is marketed as a “more secure alternative” to other platforms.