During November, 2015, I wrote a column explaining why writing secure software is so difficult. Actually, what I pointed out is that very few people ever manage to do this, with the list of successful programmers numbering in the tens of people. And when there are tens of millions of programmers in the world, having less than a hundred capable of writing secure code is a really terrible affair.
The usual examples of programmers who can write secure code consists of just two people, both with PhDs: D. J. Bernstein and Wietse Venema. They both start by being geniuses, who pay a lot of attention to detail. Then they write code that with many simple modules that each follow the principle of least privilege. Bernstein actually considers least privilege less important in his Guide to Writing Secure Programs, listing these principles:
- Eliminating bugs; this is the highlevel point; the next three points follow:
- Enforcing explicit data flow, largely done by running qmail modules in their own processes, similar to postfix
- Avoiding parsing, converting from user input to internal data structures (and back)
- Simplifying integer sematics (dealing with integer over/under-flow)
Both Bernstein and Venema have written mail servers (qmail and postfix), and both followed the same principles in their designs. They use many small programs, and each program has the minimum set of privileges required to accomplish the tasks required by each program. Data flow is via pipes or files.
For example, mail servers need the ability to read and write to files owned by any user. The wrong way to do this is to create a program with SYSTEM or root privileges, even though such a program can read or write any file. The problem with that approach is that the mail server only need to read or write very specific files, and the ability to read or write any file is a recipe for disaster–and a mistake that an uncountable number of programmers have made since the concept of a superuser appeared.
So Venema and Bernstein use more limited mechanisms, typically the ability to use the ability of a special mail group to read and write the files necessary. And only server programs that needs the ability to read and write users’ mail files belongs to the special group.
Writing software this way is much more complicated. In Postfix, seven programs are involved in receiving mail, and five more in sending mail, not including the discard and error programs for dealing with discarding or bouncing unwanted mail.
I used Postfix for many years, until it no longer made sense to run my own mail server. And while my mail server was often attacked, the worse that ever happened was that the attack stopped mail from being delivered. Well, there was the time that someone used my email address as the sender and sent out tens of thousands of spams (a Joe job). That had nothing to do with Postfix, but still was an attack of sort on my mail server, as I suddenly had thousands of bounces to deal with, each for spam sent to an email address that resulted in a bounce.
Making Security Easier
There are two things we could do to make writing secure programs easier. The first is to design software that naturally encourages the writing of many small, cooperating programs that each run with least privileges. There is already a software design movement to do that today: microservices. Microservices come very close to the design decisions used by Venema and Bernstein, although definitions of microservices focus on the designing an efficient and manageable architecture, and ignore security for the most part.
The second thing we could do is provide hardware support for security. While we have long had some hardware support, in the form of memory management and some CPU features, like no execute, or NX for preventing execution of commands loaded onto the stack or into the heap by an exploit, I’ve long believe we need more help from systems designers like Intel.
System designs that encouraged the use of microservices, by providing both isolation and fast inter-service communication would help.
CPU designers could also provide support for modern programming languages that require garbage collection. Languages such as Java, C#, and Go are manage the allocation of memory transparently to programmers, preventing many of the causes of buggy programs. Garbage collection helps programmers write better code naturally, but at the hidden expense of the extra work that must be done.