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