EDIT: I misunderstood lisp packages. I have very little experience with Common Lisp (More of a scheme guy historically) and it shows. I’m leaving this post up, but I will follow up later when I get a better knowledge of how lisp packages work. What I said about the CL-isolated package still stands and I think it offers a fantastic solution to a lot of the issues pointed out by my wonderful readers. So basically forget this post, I’ll keep the post around as a testament to a learning experience.
In my last lisp post, I mentioned lisp machines have security befits over C systems, I also suggested single address space was a good idea. Some of you guys seemed to have an issue with that, so I’m going to straighten stuff up.
So I’d first like to get into the security issue that C/UNIX has using a couple of real-world issues.
I’m going to illustrate the memory safety issue with an example: the Heartbleed Bug. If you’re reading this you probably already know what it is. A buffer overflow in a little-used module of OpenSSL; one of the most critical cryptography libraries on the web. Luckily the bug was caught by the Google Security Research people and it was never exploited in any major way.
Wikipedia does a better job describing the bug than I would:
Heartbleed is therefore exploited by sending a malformed heartbeat request with a small payload and large length field to the vulnerable party (usually a server) in order to elicit the victim's response, permitting attackers to read up to 64 kilobytes of the victim's memory that was likely to have been used previously by OpenSSL.
It overreads a char array (aka a string) and starts returning other stuff in memory.
You can blame the developers of OpenSSL for this and they have some claims to blame. At the same time, an issue like that is easy to overlook. One could argue that the language should take care of it for you.
Yeah, I know, LISP is far from the only memory-safe language, but I think most of us can agree that a memory-safe language should be the default language of the system. That requires a major transition and a replacement good enough to be worth the effort, LISP is that in my opinion.
Another big thing is type safety. Some readers of my last post commented that lisp isn’t completely type-safe. In a common lisp REPL we’re solid:
CL-USER> (+ 1 1)
2 (2 bits, #x2, #o2, #b10)
CL-USER> (+ "hello" 23434)
; Debugger entered on #<TYPE-ERROR expected-type: NUMBER datum: "hello">
It does appear that if you can compile a lisp program down in a sorta type-unsafe mode. I don’t fully understand why and I’m pretty sure no one uses it. In my opinion, lisp is type-safe. Again LISP is far from the only type-safe language, but it maybe it’s the best.
Now to get to possibly the most questioned thing I said. Single address space on a lisp machine is a good idea that we should explore.
The uneducated reader might be wondering what address space is and why we wouldn’t want a single one.
Well back in the day Computers just had open memory access. They didn’t have the overhead for memory management. A program could access and modify anything in memory. The web wasn’t around and malicious software was uncommon so most people weren’t concerned with viruses just reading everything. The significant issue was programs bringing down the entire system due to minor bugs.
For example, if a program started buffer overflowing, it could just rewrite the whole system memory.
To fix this a computer assigns a process some memory and gives it a limit to what it can write and access. A process can only write to its own memory. This helps prevents system-wide crashes triggered by single programs.
So, why do I think having a single address space is a good idea, I’m crazy, right?
Well, we’ll see. We have address spaces to:
Keep private variables private
Keep programs from accidentally burning the system down
Lisp (or any memory-safe language) more or less solves problem 2 (assuming no malicious intent, this can sorta bleed into problem 1). Problem 1 is a bit more interesting.
In lisp, we can create private and exported variables with packages. This prevents a lot of harm but isn’t perfect in it itself.
Let’s create a simple program.
(defpackage :simpledata
(:use :common-lisp)
(:export :shared-data)
)
(in-package :simpledata) ;; Create and enter a package
(defvar shared-data "Hello :P")
(defvar password "1234567890qwerty") ;; define some variables
(in-package :cl-user)
;; We're back in the main env
;; We can access simpledata:shared-data
;; We can't access simpledata:nuclear-codes
;; without changing package context
(in-package :cl-user)
You have a package that has data stuff that should be accessed and data that shouldn’t. This honestly *should* do fine in most situations. Let’s test that with some untrusted code:
(defpackage :untrusted
(:use :common-lisp)
(:export :spawn))
(in-package :untrusted)
(defun spawn ()
(print simpledata:shared-data)
(in-package :simpledata)
(print password))
This attempts to switch the package context, but if we run it:
(untrusted:spawn)
It can’t access forbidden data, because lisp silently doesn't allow it to switch package contexts:
debugger invoked on a UNBOUND-VARIABLE @53518F11 in thread
#<THREAD "main thread" RUNNING {1004AB8173}>:
The variable UNTRUSTED::PASSWORD is unbound.
Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [CONTINUE ] Retry using UNTRUSTED::PASSWORD.
1: [USE-VALUE ] Use specified value.
2: [STORE-VALUE] Set specified value and use it.
3: [RETRY ] Retry EVAL of current toplevel form.
4: Ignore error and continue loading file "/home/fulton/code/post-code/lisp-security.lisp".
5: [ABORT ] Abort loading file "/home/fulton/code/post-code/lisp-security.lisp".
6: Ignore runtime option --load "lisp-security.lisp".
7: Skip rest of --eval and --load options.
8: Skip to toplevel READ/EVAL/PRINT loop.
9: [EXIT ] Exit SBCL (calling #'EXIT, killing the process).
(UNTRUSTED:SPAWN)
source: (PRINT PASSWORD)
0]
To switch package contexts you need to be at the top level. This essentially guarantees that only a user at the REPL can peak into locked variables. With one exception: the (load)
function.
Let’s say an attacker convinces you to download a file (maybe through some vulnerably in a web browser like software) and then run something like this:
(in-package :virus)
(defun spawn ()
(load "./virus.lisp")
(in-package :cl-user)
(virus:spawn)
With virus.lisp containing:
(in-package :simpledata)
(print password)
Yeah, this method works great (for the attacker), it reads the password with no issue.
At this point, I went out searching for a solution to run untrusted lisp code and I found this:
This is a pretty simple bit of CL that essentially creates a sandbox that restricts access to possibly dangerous functions. So, we run the code in the sandbox:
(ql:quickload "isolated")
(isolated:read-eval-print "(load ./virus.lisp)")
And get:
;; DISABLED-FEATURE: The feature LOAD is disabled.
NIL
At present, cl-isolated
can only take a string and has its quirks, but it proves that true isolation of dangerous code in lisp is possible without using address spaces.
Why bother with all this no address space stuff in the first place? Well, exposing the functions and variables of the system (where safe) to other programs gives you a ton of fascinating inter-program communication possibilities without the complexity of inter-process communication programs. Your operating system essentially becomes a single, self-modifiable program.
Think about all the crazy things you can do with UNIX pipes. Then make it more efficient (no text processing needed, everything is in a list or object) and make it tweakable on a per-function level. This can awesome, but gotta get security right.
In conclusion, no, I’m not insane. Yes, LISP machines can be more secure than UNIX. Yes, single address spaces can be a good idea if your language is right.
*mic drop*
If you enjoyed the post please follow me on Twitter and mastodon. Maybe subscribe to my humble blog. If you REALLY liked the post you can send magic internet money to fultonb.eth.
If you want to argue with me shoot a message to fulton at irc.libera.chat or fulton &&at&&& fulton dot software (Fuck you, spambots) if I’m not online.