Please sign in to comment.
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...
|@@ -0,0 +1,7 @@|
|+;; Safety third!|
|+(defun safe-read (&rest args)|
|+ (let ((*read-eval* nil))|
|+ (apply #'read args)))|