Proactive Security

An exploration into systems of memory safety by skye b

For decades, computer scientists have developed a myriad of ways to solve an ever-present issue in computing: memory safety. On the lowest level, there are few safeguards in place to ensure secure access to memory. If the circumstances are right, simple memory errors can have some surprising results.

There are many approaches to this issue, but we'll look at three: an auditing system (OpenBSD C), type safety/purity (Haskell), and static analysis/ownership (Rust/Cyclone).

The first solution is OpenBSD's auditing process, which works quite well, but it requires too much manpower considering it could be done by a computer. The auditing system is a process which the OpenBSD team employs to catch common bugs by reviewing every individual file of the source code. This code is written in C, a notoriously unsafe language. The advantage of the auditing system over simply trying to catch bugs later is the proactiveness of squashing bugs before they become proven exploits; it's commonplace to see the phrase "this was fixed 6 months ago on OpenBSD" on places like BUGTRAQ. The issue with the auditing system is the cost in terms of manpower. According to the security page on their website, the OpenBSD auditing team has "typically between 6 and 12 members", and they've been auditing since the summer of 1996. Six to twelve people scanning every line of code in a file is a lot, especially considering it's error prone and the bugs are typically rather similar, meaning a computer can probably do them.

Another system we will explore is Haskell, which has safety at runtime due to type checking and a garbage collecting system, but sacrifices speed. Although Haskell is actually one of the faster garbage collecting systems out there due to Haskell's immutable data, as specified in the paper Parallel Generational-Copying Garbage Collection with a Block-Structured Heap, a garbage collector will always introduce a cost, because the program has to go back to collect all the unused memory every couple minutes. This cost can be heavily optimised into performance on par with or faster than C/C++ code, but it requires replacing a lot of the ergonomic solutions which define Haskell code, such as list comprehension, typeclasses, higher order functions, and nested application. Although the code is safe, it doesn't necessarily meet the speed requirement one desires, especially for systems applications which must be as fast as possible.

Borrow checking (as seen originally in Cyclone but now, more popularly, in Rust) is as fast as manual memory management, yet as safe as garbage collection. The first thing to understand when looking at this is the two data structures used for memory in the OS: the stack and the heap. The stack and the heap are both different ways of accessing memory. The stack is fast, but has a fixed size. The heap, on the other hand, is dynamically sized, meaning it can hold a variable number of items, but slower because it needs to look up memory addresses to use it. Rust, like C, gives complete control over the stack and the heap, meaning you can make micro-optimizations to your code to get high performance. The special part about Rust, in comparison to C, is the concept of ownership or borrow checking. Ownership is a method of memory management utilized by Rust which is a "set of rules that the compiler checks at compile time”, according to the rust book. It is designed to give the speed of explicit memory management with the safety of garbage collection by having the compiler audit your code for you. The rules of ownership dictate that all data has one owner at a time, and that it can either have one mutable reference or any number of immutable references. This prevents data from getting used after free, null pointers, dangling pointers, and other common issues that can often be exploited. Because these checks are done at compile time, it has no effect on performance at runtime, meaning your programs will be as fast or faster than those written in unsafe languages like C.

Thus, when comparing an auditing system, a garbage collector, and borrow checking, the model provided by Rust's static analysis and borrow checking provides the best model for secure systems due to its combination of superior speed and automatic safety.