The Security Struggle with Lisp
Anil Somayaji, July 29, 2021
Recently I read the transcript of Rich Hickey's 2017 talk on Effective Programs, which really was a talk about the benefits of Clojure (as a kind of Lisp and as its own thing). He made several points that resonated with me, including:
- Type systems are partial, incomplete specifications of program semantics, and so time invested in using complex type systems are at most a partial solution, one that can have a high cost.
- Strict type systems can cause problems when trying to express communication in heterogeneous, distributed systems.
- Programmers can be sucked in to solving puzzles that don't actually help address the problem at hand.
But what struck me about this essay was that he discussed communication and distributed systems without making a single reference to security or trust. And this got me thinking.
I've long been a fan of Lisp and Lisp-like systems, and when I develop in other languages (which is almost always) I find myself trying to use techniques that I first learned with Lisp and Scheme. However, in the world of security, the values that are celebrated by Lisp-like systems are denigrated if not outright rejected. For example, many security practitioners have a strong aversion to treating code as data, because then attackers can inject data and turn it into code, subverting the security properties of the system. As a security measure, data pages in process memory are no-execute by default nowadays and code pages are read only. Systems that want to treat data as code, such as just-in-time compilers, must constantly switch pages between being writable and executable. Anything that is truly Lisp-like, however, treats code as just another data structure to be manipulated by code and even includes facilities for dynamically changing the semantics of code on the fly. Such mechanisms can be amazingly powerful in the hands of a skilled developer; the same mechanisms, however, can be equally powerful in the hands of an attacker.
In his talk, Hickey discusses how Clojure is good at interacting with other systems because it assumes an open world while classic strongly-typed systems assume a closed world. If the entire solution is in a given language, then strong typing can be used to assert relationships consistently throughout the code base. In an open world, however, your code is always a partial solution embedded in a larger system, and as such, you'll often have to work around the type system in order to express interconnections with those other pieces.
I'm not going to argue against the utility of type systems here, as I can see their benefits in many contexts. What I do want to point out, though, is this open/closed world dichotomy is at the heart of computer security. In effect, when we secure a system we turn it into a "closed" system, in the sense that its semantics cannot be changed by unauthorized outside input. Exploits turn this assumed closed system into an open one, making it a system that an attacker can now modify to their own ends. Security practitioners thus embrace rigidity at every opportunity, limiting semantics to what has been planned in advance. Lisp, a seminal tool in the study of artificial intelligence, instead embraces the possibility that old code can be used in new, surprising ways, as that flexibility is a hallmark of intelligent systems.
I would say that a central concern of my research career has been to figure out how to resolve this conflict. How can we have flexibility and security at the same time? How can we secure a system when its semantics aren't fixed? How can we have protection boundaries without those boundaries limiting the scope of what we create?
What I hadn't ever articulated is that I'm trying, in part, to reconcile the culture of Lisp with the needs of security. I think this framing is perhaps more accurate than saying I'm trying to bring the ideas of biology to computer security, as any ideas that we find in biology really come from us. Life just is, it doesn't have to deal with higher-level concepts or abstractions. The high-level ideas we see in biology are as much a function of our own biases and limitations as they are of anything inherent in living systems. Those same ideas show up elsewhere, and so it is important that we acknowledge their true basis. Perhaps this is something to discuss further in another essay!