The biggest source of security problems in software comes from misunderstood and/or poorly designed programming abstractions (take strcpy(3)). The better our abstractions can be - the more tailored to our exact needs they are - the more secure our software gets. Lisp is the right technology for making those abstractions.
To get the best possible use out of nuff it is necessary to run it with root privileges. As with any piece of software - especially when attached to a potentially malicious internet - you should be concerned about this.
Many pieces of security software perform complex sequences of tasks requiring root privileges. In particular, a lot can be gained by using so called "raw sockets" which allow a program to parse and craft network packets in ways the operating system didn't intend. While this is very powerful and is used to great effect in tools like nmap and hping, the root privileges that raw sockets require can turn any small programming flaw into a serious system-wide security problem. Consider the recent traceroute remote vulnerability or the myriad of remotley exploitable flaws in ethereal.
Even programs that run without root privileges can be exploited in dangerous ways if the program wasn't designed with security in mind. Since one of nuff's goals is to provide a safe, reliable interface for experimenting with network protocols, nuff takes a pro-active approach to security. This document describes some of nuff's security features and the considerations we've given security.
Since nuff is a scheme system, we have a solid, well designed run-time type checking system. Barring any bugs in our scheme system, this provides a large amount of protection for a nuff script writer. Some of the safety checks that scheme employs are:
Run-time verification of all array and string references. This means that it should be impossible to write to the memory outside the allocated bounds for any object - greatly reducing the possibilities for buffer over/underflows.
Run-time verification of format parameters. The Common Lisp format macro that we use in our scheme system is much more powerful than the crippled-by-comparison C printf(3) formatting function. While this makes writing output routines in nuff very convenient, it can also open up possible attack vectors when used incorrectly. Fortunatley, our format implementation does a large amount of run-time type checking which generally makes poorly written format directives difficult to exploit and easy to detect and debug.
Automatic memory management. Lisp was the first language to employ automatic memory management. While the efficiency implications of this technique can be endlessly debated, the safety features it provides simply cannot. Automatic memory management makes your programs safer by reducing the largest source of programming errors in non-managed languages. Forgetting to free data structures, accidentally freeing them twice, forgetting to close no-longer needed file/socket descriptors, etc are errors that don't occur in a managed language like scheme.
Nuff tries to adhere to the principle of least privilege in all its scripts. Since all nuff scripts are lists of scheme s-expressions, it is easy to write code to parse and process them. When the nuff compiler does this it takes a very strict approach to privileges. In general, unless the compiler detects an obvious need for some capability it doesn't grant the program permission to use that capability.
Some examples are:
Process privileges. Although many nuff programs require root privileges for raw socket access and so on, the majority of a nuff script does not run with root privileges. The nuff programmer can use the privileged macro to have certain nuff expressions evaluated before all process privileges are dropped. Typically, only a pcap descriptor and/or a dnet raw socket are opened before nuff permanently revokes its process privileges by changing its UID and GID to the system's "nobody" user.
NOTE: This is not entirely implemented in nuff yet.
We are planning to, by default, automatically chroot(2) the majority of nuff scripts into a contained directory. The plan is to parse nuff scripts and try to find the files, or file-prefixes, that the nuff script obviously wants to use and chrooting to the furthest-up branch on the filesystem tree possible (even to an empty directory). We will then re-write file-open commands to correspond to the new root.
Nuff scripts that want to perform more complex file-based I/O will have to explicitly register such desires with the compiler before the chroot().
Pcap locking. Many experienced network programmers are unaware that pcap descriptors can be reconfigured through ioctl()s even after a process has dropped its process privileges. This means that if your unprivileged process has a pcap descriptor with a very specific filter, say, "icmp and src some.host", an exploit could still allow an attacker to change the filter and sniff more valuable traffic. On systems that support it, Nuff will "lock" (via the BIOCLOCK ioctl()) all of its pcap descriptors to prevent this type of reconfiguration.
In network protocols there is a frequent need for random numbers. Generating session IDs, sequence numbers, and source ports are some typical examples.
The importance and ubiquity of random numbers in network protocols makes them attractive targets for attacks. If an attacker is able to predict the "random numbers" used by an application he or she can often perform all sorts of mischief.
Nuff tries to prevent this by using strong cryptography. Instead of implementing this ourselves, we decided to use libdnet's random number interface. libdnet provides a reliable, well tested interface to the operating system's cryptographic randomness pool and strong PRNG which nuff uses for all random values.
Nuff makes it easy to perform rudimentary verification on packets at all stages. In fact, when you use the parsepaq macro this is the default behaviour. The code that parsepaq generates will unravel the packet layer by layer running it through our verification closures. The verification closures are just closures around the expressions provided in the layer-specs and generally verify a minimum length of a packet and/or a protocol number in the parent layer.
Although parsepaq usually doesn't guarantee that a field in a packet will have a sensical value, it will always guarantee there is a value and the packet is properly structured.
Furthermore, due to the nature of shared strings, even parts of the packet being parsed will be inaccessible to different parts of the macro expansion. In other words, if you use parsepaq to extract a layer out a packet and pass that layer to a function outside your lexical scope, even though that layer shares memory with the original packet, the function will not be able to access the parent layers. See the note on shared strings in "nuff doc language" for more details.
Since nuff implements a co-operative multi-tasking environment, it is able to make more guarantees about run-time behaviour when it comes to multiple tasks accessing the same data structures concurrently.
The first thing of note is that there is little need for "locks" or other synchronisation primitives in nuff. Any operation that doesn't require I/O will never be pre-empted so there is no need for any sort of locking mechanism. As a running continuation, we can always be sure that all other continuations are blocked awaiting I/O. Even if data arrives for them, or their send operation completes while we are running, nuff will not schedule them to run until we block on I/O ourselves. Operations that do require I/O should generally be done concurrently.
Correct concurrent programming is difficult in all languages.
Whether it is simply the nature of the problem or if we just haven't yet invented decent enough abstractions remains to be seen. Correct concurrent nuff programming is also difficult, but we offer a rich set of flexible primitives that, in our opinion, make concurrent I/O based programming more tolerable. We are still experimenting with the programming interfaces used for concurrency and we have high hopes that, thanks to the beautiful and extensible nature of lisp, we will eventually reach a stable API optimised for both power and safety.
On a semi-related topic, a future nuff research project will involve continuation-based non-determinism as a convenient Domain Specific Language for creating so-called "protocol fuzzers". These are programs that try brute force attempts of a protocol against a daemon or network stack in order to discover odd behaviours and security weaknesses. See Graham's "On Lisp" for information on non-determinism and the back-tracking search it uses.
All material is © Doug Hoyte and/or HCSW Labs unless otherwise noted or implied.