Skip to content
Browse files

safety

Common lisp's READ function parses a stream of characters and returns
lisp objects.

Now, by object, I'm not talking Object Orientated Programming (OOP),
but rather one of the many different types of things that lisp can
work with such as strings, numbers, and lists (well, really 'cons'
cells but that is to be described elsewhere).

With no arguments, READ reads from the keyboard:

> (read)
2.718    <- you type this in
2.718    <- what READ returns after converting to a lisp object

So READ parsed the stream of characters '2' '.' '7' '1' '8' and
returned the floating point number 2.718.

> (read)
foo
FOO

READ parsed the characters 'f' 'o' 'o' and returned the symbol FOO.

> (read)
"foo"
"foo"

> (read)
(we love lisp)
(WE LOVE LISP)

You get the idea.

Now, how the READ function does this is with the use of a thing called
a 'readtable' which contains all the rules for how to deal with
characters it is trying to parse.

In the standard readtable, the character sequence '#' followed by '.'
causes the reader to EVALuate the results of calling READ on the rest
of the sequence of characters.

Clear? Hmm....

> (read)
(+ 1 2)
(+ 1 2)

The reader parses '(' '+' ' ' '1' ' ' '2' ')' into the list (+ 1 2).

> (read)
 #.(+ 1 2)
3

Note that the extra space above before the '#' is so that git doesn't
think the line is a comment and remove it.

The reader read '#' followed by '.' and then recursively called itself
to READ the sequence of characters '(' '+' ' ' '1' ' ' '2' ')' into
the list (+ 1 2) and then called EVAL on this list to get the result,
3.

> (read )
(+ #.(+ 1 2) 4)
(+ 3 4)

> (read)
 #.(+ #.(+ 1 2) 4)
7

Now, this is all fine and dandy and useful *except* when this behavior
is not desirable. One very common case is when reading things from a
user or some other source where you do not have complete control as
this functionality allows for running *anything* in the system via
this input stream of characters.

BAD BAD things can happen if you do not account for this.

> (defun my-moon-weight? ()
    (format t "What is your weight in pounds?~%")
    (let ((weight (read)))
      (format t "On the moon you would weigh ~F pounds.~%" (/ weight 6))))
MY-MOON-WEIGHT?

> (my-moon-weight?)
What is your weight in pounds?
165
On the moon you would weight 27.5 pounds.

This function does not check the validity of the input but that seems
OK because at least it will error out pretty quickly on bad input.

> (my-moon-weight?)
What is your weight in pounds?
one cheeseburger
ERROR: Argument X is not a NUMBER: ONE

All seems OK, but now see...

> (defun boo () (princ "Mwaaahhaahaa!!")
BOO

> (my-moon-weight?)
What is your weight in pounds?
 #.(boo)
Mwaaahhaahaa!!

So the user has been able to run arbitrary code from this simple
little function, which might have been happily linked to a web page or
something open to the world. Oops.

The standard readtable has a variable *READ-EVAL* which controls
whether or not the sequence '#' '.' works or errors. Note that the
default is T.

This commit introduces SAFE-READ, which is a shortcut for binding
*READ-EVAL* to NIL.

> (safe-read)
 #.(boo)
ERROR: can't read #. while *READ-EVAL* is NIL

The academy will always use SAFE-READ when reading from the keyboard
or anywhere that we do not have complete control of both the
horizontal and the vertical.
  • Loading branch information...
1 parent 9b83f9d commit d45bb8453eb8815d1bb229e2c5ff72ce265ee2d0 @whalliburton committed Dec 21, 2011
Showing with 8 additions and 0 deletions.
  1. +1 −0 academy.asd
  2. +7 −0 safety.lisp
View
1 academy.asd
@@ -30,6 +30,7 @@
(:file "common-lisp")
(:file "naked-repl")
(:file "compliments")
+ (:file "safety")
#+sbcl (:file "sbcl-nuts-and-bolts")
#+sbcl (:file "git")
#+sbcl (:file "log")
View
7 safety.lisp
@@ -0,0 +1,7 @@
+(in-package :academy)
+
+;; Safety third!
+
+(defun safe-read (&rest args)
+ (let ((*read-eval* nil))
+ (apply #'read args)))

0 comments on commit d45bb84

Please sign in to comment.
Something went wrong with that request. Please try again.