On the reader macro #=

106 views
Skip to first unread message

samppi

unread,
Aug 25, 2009, 5:07:57 PM8/25/09
to Clojure
#= is a real Clojure reader macro. It often shows up when using *print-
dup*:

Clojure 1.0.0-
user=> (binding [*print-dup* true] (println {:a 3, :b 2}))
#=(clojure.lang.PersistentArrayMap/create {:a 3, :b 2})
nil
user=> #=(clojure.lang.PersistentArrayMap/create {:a 3, :b 2})
{:b 2, :a 3}

It's undocumented in http://clojure.org/reader. What is its name? What
does it precisely do?

Richard Newman

unread,
Aug 25, 2009, 5:51:05 PM8/25/09
to clo...@googlegroups.com
> It's undocumented in http://clojure.org/reader. What is its name? What
> does it precisely do?

It's "EvalReader". What it does is cause the expression to be
evaluated at read time:

user=> (read-string "(+ 5 #=(* 9 9))")
(+ 5 81)

You can prevent this occurring by binding *read-eval*:

user=> (binding [*read-eval* false] (read-string "(+ 5 #=(* 9 9))"))
java.lang.RuntimeException: java.lang.Exception: EvalReader not
allowed when *read-eval* is false. (NO_SOURCE_FILE:0)

-R


Richard Newman

unread,
Aug 25, 2009, 5:58:39 PM8/25/09
to clo...@googlegroups.com
Incidentally, you can find this stuff out by reading the source, if
you know where to look. It's a reader macro, so LispReader.java is the
best place to start. Look for the metachar '=', which crops up on line
91:

http://github.com/richhickey/clojure/blob/14316ae2110a779ffc8ac9c3da3f1c41852c4289/src/jvm/clojure/lang/LispReader.java#L91

dispatchMacros['='] = new EvalReader();


then just skip ahead to the EvalReader class definition if you need
additional context to understand what's going on.

HTH.

-R

samppi

unread,
Aug 25, 2009, 10:44:40 PM8/25/09
to Clojure
That's great! Thanks a lot for the explanation.

On Aug 25, 2:58 pm, Richard Newman <holyg...@gmail.com> wrote:
> Incidentally, you can find this stuff out by reading the source, if  
> you know where to look. It's a reader macro, so LispReader.java is the  
> best place to start. Look for the metachar '=', which crops up on line  
> 91:
>
> http://github.com/richhickey/clojure/blob/14316ae2110a779ffc8ac9c3da3...

John Harrop

unread,
Aug 26, 2009, 1:13:40 PM8/26/09
to clo...@googlegroups.com
This is important to know about for security reasons, also. Specifically, if you are receiving Clojure data structures in text form over the network, and don't set *read-eval* to false, you're vulnerable to a "Clojure injection attack". Someone could send you "(+ 5 #=(System/exit 0))" as a denial-of-service attack, just for starters.

I doubt there's a way to make it safe. There's probably no way to force those expressions to run in an applet sanbox, at least without massive kludging. You'd have to vet the strings first, using some non-Clojure-reader parser. Easier to use the Clojure reader and then walk the resulting data structures looking for, say, special sentinel keywords that should be substituted with other things, or that flag something about the following item (say, that it should be converted to a SortedMap).

For storing stuff locally the "EvalReader" should be safe, unless your program runs with elevated privileges compared to the user who runs it (unix setuid or equivalent). In that event though there's a possibility of it being exploited for local privilege escalation. Arbitrary Clojure and Java code could be submitted to be run at the higher privilege level.

John Harrop

unread,
Aug 26, 2009, 1:17:09 PM8/26/09
to clo...@googlegroups.com
On Wed, Aug 26, 2009 at 1:13 PM, John Harrop <jharr...@gmail.com> wrote:
This is important to know about for security reasons, also. Specifically, if you are receiving Clojure data structures in text form over the network, and don't set *read-eval* to false, you're vulnerable to a "Clojure injection attack". Someone could send you "(+ 5 #=(System/exit 0))" as a denial-of-service attack, just for starters.

Interesting result from testing this:

user=> (read-string "(System/exit 0)")
(System/exit 0)
user=> (read-string "#=(System/exit 0)")
ClassNotFoundException: System
user=> (read-string "#=(java.lang.System/exit 0)")

REPL is disconnected.

Strange that java.lang is not apparently imported in whatever environment the EvalReader uses. Doesn't stop it being a security hole if accessible over the network though. :)

Chouser

unread,
Aug 26, 2009, 2:16:14 PM8/26/09
to clo...@googlegroups.com
On Wed, Aug 26, 2009 at 1:13 PM, John Harrop<jharr...@gmail.com> wrote:
> This is important to know about for security reasons, also. Specifically, if
> you are receiving Clojure data structures in text form over the network, and
> don't set *read-eval* to false, you're vulnerable to a "Clojure injection
> attack". Someone could send you "(+ 5 #=(System/exit 0))" as a
> denial-of-service attack, just for starters.

> I doubt there's a way to make it safe. There's probably no way to force
> those expressions to run in an applet sanbox, at least without massive
> kludging.

I'm pretty sure clojurebot in the #clojure channel does exactly this kind of
sandboxing for both read and eval.

--Chouser

Reply all
Reply to author
Forward
0 new messages