On Mon, Feb 28, 2011 at 5:59 AM, Jules <jules.gosn...@gmail.com> wrote:
> Unfortunately, I'd like it to be a open set, just as I would like the
> system's types to be - this was one of the choices driving Clojure
> over Java as the implementation language.

The simplest thing I can think of in that case is to send function
*source* back and forth as Strings contaning s-expressions and use
(eval (read-string foo)) to obtain function objects. Icky and of
course a security can-o'worms since someone can potentially send your
app "(fn evil-virus [] (.format (File. "C:/")) ...)" or whatnot. You'd
need some sort of authentication scheme, probably based on PKI and a
key pair per node, if an open port was going to be internet-visible
that could receive these messages.

Dicking about with ClassLoaders or modifying Clojure itself are the
other two possibles. The former would probably entail getting at a
fn's bytecode and schlepping it around as a ByteBuffer, loading it as
a class at the other end and using reflection to get an instance call
.invoke on it. The same security concern arises, since the bytecode
for an EvilVirus.class could be sent by a hostile node. You might be
able to mitigate it without heavy crypto, though, in this case, by
using a ClassLoader at the receiving end that sandboxes the code it
loads, similar to the ClassLoader used for browser applets. The
modifying-Clojure route is simpler: just tack "implements
Serializable" on clojure.lang.IFn and use RMI. I'm not sure how nicely
RMI will play with HOFs, though.

> I've also given a little thought towards using URLClassLoader and an
> embedded http server to allow a client to transparently fetch required
> classes on demand.

This carries the same security implications as scenario 2 above.
Sandbox the remotely-loaded code, at least if generic Internet hosts
can potentially send it, or use HTTPS to authenticate its origin.

The Clojure developers might want to consider adding a (safe-eval) to
future versions. Ideally, (safe-eval) would refuse to generate any
bytecode that invoked Java methods directly (other than by way of
inlined functions defined externally) and would wrap results in an
environment where (io!) would explode. So (safe-eval '(+ 2 3)) would
produce something somewhat like (eval '(dosync (+ 2 3))), perhaps
minus much of the dosync baggage; (safe-eval '(fn [] (+ 2 3))) would
have to additionally wrap the function body (as otherwise the output
of (safe-eval '(fn [] (trash-hard-disk))) would not bat an eye when
invoked); and (safe-eval '(.length "foo")) would fail, likewise
(safe-eval '(fn [s] (.length s))). It would also have to disallow some
things like set! and some of the namespace functions that could be
used to corrupt the runtime, and alter, ref-set, swap!, send,
send-off, reset!, compare-and-set!, and def. Basically, the idea is
that safe-eval be a pure function and any function object generated
directly or indirectly by it be a pure function. So it can be used to
safely exchange pure-functional bits of code around. That would not
totally eliminate security risks from code-transmission, of course; a
pure function can still blow the stack, blow the heap, run for ages
hogging the CPU, or simply fail to fulfil its intended contract in
various ways, but it would get rid of pretty much all of the really
nasty risks such as arbitrary hard drive scroggage, password sniffing,
credit card fraud, virus-infecting, and what-have-you, leaving mostly
denial-of-service vulnerabilities. And someone who hated your node
could as easily just pingflood the application's open port anyway.

> Understood - I just used a bad turn of phrase. What I meant to say was
> that, whilst I can sprinkle any amount of metadata around a defrecord,
> defrecord itself only makes use of a certain amount of it and, as far
> as I can tell (and it would help me if I was wrong on this), once the
> new record type has been defined, there is no way to access non-
> standard metadata that was present at defrecord time : e.g.
>
> user=> (set! *print-meta* true)
> true
> user=> (def ^{:my-tag [1 2 3]} foo nil)
> ^{:ns #<Namespace user>, :name foo, :my-tag [1 2 3], :line 2372, :file
> "NO_SOURCE_PATH"} #'user/foo
>
> OK - my metadata preserved and now available to my program - but
>
> user=> (defrecord ^{:my-tag [1 2 3]} Foo [a b ^{:my-other-tag [4 5 6]}
> c])
> user.Foo
> user=>
>
> and
>
> user=> (Foo. 1 2 3)
> #:user.Foo{:a 1, :b 2, :c 3}
> user=>

The metadata is being attached to symbols in the record definition,
not to your records themselves. You'd need a modified constructor for
that:

(defn my-constructor [a b c]
  (with-meta (Foo. a b (with-meta c {:my-other-tag [4 5 6]})) {:my-tag
[1 2 3]}))

If the metadata contents need to be more variable, the constructor
will need added parameters (in the simplest cases just the metadata
maps themselves). Note that the objects used for c would have to
accept metadata in this example, so vectors and the like would work
but the integer 3 would not.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to