The Internet of Things (IoT) is still the Wild West. Anyone can wake up some morning and declare a new idea for a sensor, a new protocol, a new product, a new application… pretty much anything – as long as it has “IoT” in the title of the press release.
And that’s great for spawning fresh ideas. But as civilization encroaches on this untamed territory, notions of governance are being suggested for limiting some of the possible excesses. We’ve talked about protocol standards numerous times before, but there are a couple of new, different standard efforts underway that relate not so much to agreement on how to behave, which is the role of protocols, but rather to the quality of the systems. For today’s discussion, the focus is on security.
We spent a fair bit of ink on security earlier, but there’s been little in the way of “enforcement” or other means of deciding whose security is good and whose is vulnerable. Of course, simply publishing the problems and the names of the weaklings would be like issuing a map to hackers, letting them know where the shortcuts are. Instead, these latest efforts allow product makers to achieve certification before launching a product.
Each of these efforts has a different scope. At the high level, Underwriters’ Laboratories (UL) – you know them from the label on your toaster – has addressed system-level behavior. At the low level, MISRA (originally the Motor Industry Software Reliability Association, with scope now extended far beyond autos) has added some rules to their C language restrictions that are intended to strengthen security.
Before we dig into them, it should be noted that there is some brewing dissatisfaction with UL’s decision to charge for its certification documentation. I’m not going to wade into that debate, but it does limit what I can say here. UL was good enough to send me a carefully watermarked copy of their rules, under the expectation that I will not “…reprint (in part or in whole) or excerpt, reproduce, share or make available for download the information in this document.”
So all specific words in the doc are off limits, meaning I need to beat around the bush in my attempts to explain what’s in the doc. While I’ll be describing some of the requirements, they will be my words, and there will be missing details. You’ll need to pay for the doc to get those details.
But, then again, even with the MISRA C thing, I will be providing only an overview, so you’ll also need to acquire that doc to make specific use of it. Both organizations can rest assured that this article won’t reduce (and might even increase) sales of the doc.
UL, at its core, is all about minimizing risk. It started with insurance companies trying to find ways to make products safer so that the products would cause fewer injuries and start fewer fires, thereby reducing the number of claims the insurance companies would have to pay out. So the underwriters formed Underwriters’ Labs to set standards and handle the grunt work of certifying that products meet the standards.
It would seem that most of the UL efforts have focused on product safety, but this latest effort zeroes in on security. While that may not be a safety thing (directly), it does get to risk and liability and so could reasonably be seen to be within UL’s purview.
They presented at last week’s IoT DevCon, and the fundamental strategy they use for security is no different from what they apply to everyday safety. There are three layers:
- Physical properties, which means the hardware
- Logical properties, which means the software
- Procedures – things like version control, key management, and security during manufacturing
At the top of their list is a thorough set of documentation. Product docs describing the function, all ports or inputs, all source code, build info, and binary or byte code. Design docs detailing any risk analyses and mitigations. Usage docs covering user instructions, including how to use securely; interfaces and communications protocols; software component version info; event logging. There’s an interesting standard that comes up a few times that says that some security function – like authentication – must be as hard to get around as it would be to guess your way through – like guessing the key.
There are a number of rules, and, while each product doesn’t necessarily have to pass all the rules, there’s a process for explaining the rationale behind omitting rules. Some of the things the rules cover are:
- The need to authenticate when security is involved, whether locally or remotely, wired or wireless;
- Username and password quality enforcement;
- Role-based access and privileges;
- Interrupted network sessions;
- Encryption of data;
- Single-use keys;
- Update validation and rollback;
- Security event logging; and
- Requirements to change factory-default security settings.
The product should be free of basic vulnerabilities, including no known viruses in the object code, no lurking malware, and no bizarre responses to bad input. Code (source and executable) will be subject to static analysis to catch any of these things. And the product will undergo penetration testing to see if they can hack in.
Ultimately, outside this specific security document, they’re proposing a star-based rating system – kind of like Energy Star – so that your average buyer can make a decision (in the absence of outright regulatory requirements) with some understanding of how secure a product is (without giving specific directions to a hacker on what the weaknesses are).
Do You C What I C?
UL looks as high level as MISRA C looks low level. MISRA came about through the realization that the C language is incredibly powerful – and from that power comes weakness. If people could start the language over today, there would probably be a lot of changes, but it’s too late for that. C (and its equally impenetrable younger sibling C++) are everywhere, the darlings of low-level programming (and standing in stark contrast to internet-style programming via the REST architecture).
It’s almost like a 2nd-amendment thing. People demand to have access to the power of C, but then again, there are definitely some users that become dangerous when given that access. You can’t get rid of C, but you can institute something akin to sensible background checks.
When writing code that will control how an automobile operates, you don’t want code written by one of those guys in whose hands C becomes a dangerous tool. This isn’t about malevolent intent (although it could be); it’s about the many ways to use the C language that have unintended results. And the last thing you want when a car is tooling along at 70 mph is an unintended result.
So MISRA put together some restrictions on the C language to block some of the riskier ways to use C, and it’s generically referred to as MISRA C. There’s also a MISRA C++. This has mostly been confined to the domain of safety-critical systems, but they have now turned their eyes to security issues, and they’re extending their scope to cover IoT devices – which could make MISRA more relevant to a wider body of designers.
What they’ve done is to adopt a set of 14 security guidelines from ISO/IEC 17961:2013 into MISRA C:2012. I became aware of this through an LDRA announcement indicating that their tools could now enforce these rules. If you’re looking carefully, you might wonder how the heck the 2012 MISRA rules were able to incorporate guidelines from a standard that happened a year later. Well, that’s because this was done through an addendum (Addendum 2). The addendum adds onto the 2012 standard, but it was added in 2016. So there has been no time warp.
The big target of most, if not all, of the rules is buffer overrun. Addressing entry 20 of a 10-entry structure, thereby invading neighboring memory space, is not considered neighborly behavior. It amazes me that such overruns just happen to peek into useful parts of memory (just think of all of the crap scattered all over the memory, and how little of it would be of use to a hacker), but it has been a proven, profitable technique, and so this attention appears to be warranted.
Item 1 on their list is a directive, Dir 4.14. And it pretty much says what we learned back in CS 1: “The validity of values received from external sources shall be checked.” Actually, this is even looser than the CS 1 thing, which says validate all inputs. The thing is, if you’re writing a program and have complete control over flow and data, then you can potentially save some execution time and code size by not validating, for instance, every argument passed to every function every time (since it can take more than one check to thoroughly vet a value).
So this concept of “external” makes sense in this respect. But, in an era of open source and software components and overall reuse, one never knows where the data will come from once the code has left its original setting. The guy writing the original program isn’t likely to feel responsible for making his or her code completely safe for any old bonehead pulling out code and putting it into some other program. Someone reusing someone else’s code probably isn’t going to want to delve into the copied code to make sure that the reuse hasn’t changed the original assumptions on sources of data and arguments. So ownership here feels a bit messy.
Items 2 and 3 deal with arrays as function parameters. Because arrays get converted to pointers by compilers when passed as arguments, sizeof no longer works. If you’re relying on it to keep indices in bounds, you may be in trouble (meaning the bounds may be wrong, meaning you may trample your neighbor’s lawn). Separately, using ctype must come with an unsigned int or EOF value to ensure that the access doesn’t exceed the size of the ctype lookup table.
Item 4 bans memcmp for comparing strings. This function doesn’t return just a true/false answer, but gives a positive or negative answer (the difference between the compared strings) or 0 (for a match). That information can be useful to someone poking around. Not only that, but the execution time of the function can give a hint as to how close you are to a match (since obvious fails fall out faster). They recommend a constant-time comparison (although they don’t say what that might be).
Rule 5 says that pointer arguments to memcpy, memmove and memcmp have to be of appropriate types.
Rule 6 gets into a murky area. I’ll quote here: “The pointer arguments to the Standard Library function memcmp shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum type”. This raised a question for me about the word “essentially” in this context. Googling gave me pretty much nothing.
Well, this turns out to be a MISRA thing, and it’s not a brief explanation. There’s a six-page PDF describing what “essentially” essentially means. The problem is, again, the C language and various inconsistencies and potential misinterpretations that can occur when deciding if a type is appropriate. The issues can be summarized as:
- Inconsistency of type with integer promotion
- Limited constant types
- The fact that char variables are intrinsically int type
- The lack of a true native Boolean type
- The lack of bit fields in the formal ISO C standard
- The fact that enumeration constants are intrinsically of int type and can take on any int value.
So the “essential” notion gets to resolving issues with types that are essentially some other type. That’s about the easiest way to say it. Thanks to LDRA for finding the appendix that lays this out (Appendix D of MISRA C:2012).
Rule 7 is about string handling, and it simply says that string accesses should not exceed the bounds of the string. That means validating string lengths as you go. Rule 8 is similar, except that it deals specifically with validating a size_t value before using it to access a string.
Rule 9 deals with pointers returned by the localeconv, getenv, setlocale, or strerror functions. These return standard strings – or rather, pointers to the strings. But they’re not pointers to some global place where the strings are stored; instead, a copy of the appropriate string is pulled out of the global store, and a pointer is returned to the copy. The concern here is that someone could monkey with the bounds of the string copies. So you have to treat the pointers as if they were of a const-qualified type.
Rule 10 is related and says that you can’t reuse string buffers returned by asctime, ctime, gmtime, localtime, localeconv, getenv, setlocale or strerror. The concern is that someone could get in and change the contents of the buffer, and that altered value would then be picked up in the reuse case.
Rule 11 accounts for the fact that it’s not a good idea to use EOF for anything other than comparing to the result of a function that may return EOF. Trying to use EOF directly can cause implementation-dependent bizarreness.
And… finally… Rules 12-14 are about clearing errno values before calling a function that could set an error number, and then testing them against zero immediately after they’re returned. This is, again, to guard against someone manipulating that number sometime between when it’s returned and when it’s used.
OK, dang, I’m ready for a beer. You can find all of the details in the respective documents. I can’t link you directly, but I do link to the org pages below.