Is it safe to `read` untrusted input?

60 views
Skip to first unread message

Ryan Kramer

unread,
Feb 28, 2021, 2:50:05 PM2/28/21
to Racket Users
I want to send some Racket structs across a network. I know that I can use prefab structs, serializable-structs, or even `eval` with a carefully curated namespace. I was trying to think of security problems with the eval approach and now I've become more afraid of `read` than I am of eval. And read seems necessary for all 3 approaches.

The first concern I thought of was cyclic data. My code assumes there are no cycles; if an attacker can get me to process cyclic data my server will probably loop forever or crash. This can be solved by setting `read-accept-graph` to #f... I think. Right? (I guess another solution is "you need to validate the input" which is fair, but it's easy to forget or make a mistake.)

This caused me to notice other `read-accept-***` parameters that looked scary (-lang, -reader, -compiled). I don't know if there is an attack vector here, but I felt safer turning them off also.

Now I'm thinking that even if I can get it working safely today, Racket would be well within its rights to make enhancements to the reader in the future. So someday there might be new parameters that I would want to turn off to preserve my definition of "safe", and I have to remember this when I upgrade.

All this makes me think that `read` is not quite the right tool for the job. But it's close. If there were a version of read that accepts nothing by default and requires the caller to opt-in to everything they want, that would probably be perfect.

I could use JSON or XML, but that just seems silly when you have a Racket client talking to a Racket server.

Are my concerns founded? Are there any existing solutions? Thanks for any advice.

Robby Findler

unread,
Feb 28, 2021, 4:04:19 PM2/28/21
to Ryan Kramer, Racket Users
Leaving aside bugs, the intention with those parameters you mention (-lang, -reader, -compiled) is to help with security. They certainly would allow for code execution and they are off by default precisely because they allow that. I think that the general principle (read should, with the default parameter values, not evaluate any code) is one that is unlikely to change.

Robby

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/a2580765-3cc2-482b-8d20-f62dc1e1dc91n%40googlegroups.com.

Sage Gerard

unread,
Feb 28, 2021, 4:20:46 PM2/28/21
to racket...@googlegroups.com

Does call-with-default-reading-parameterization help?

https://docs.racket-lang.org/reference/Reading.html?q=accept-compiled#%28def._%28%28lib._racket%2Fprivate%2Fmisc..rkt%29._call-with-default-reading-parameterization%29%29

The parameters you are seeing re: accepting -lang, -reader are for when the reader sees a reference to an extension that it can load and use. In that sense, code defined for that extension would execute so that the reader is prepared to process upcoming bytes. So if you are worried that the reader would execute code as a side-effect of reading, then yes, that can happen. Unsure what the #~ does, so I can't speak to that.

Defining a procedure that flips off all parameters related to extensions is a good step, but I do not know if the linked example serves as a true "Deny All" equivalent. The docs make it seem like it does. Even so, I think there are other aspects to a zero-trust policy such as the values of `current-security-guard` and `current-code-inspector`... but you'll get to that.

Using a different data format might work, but the library itself leverages the reader, then you still would care about enforcing these restrictions anyway.

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/a2580765-3cc2-482b-8d20-f62dc1e1dc91n%40googlegroups.com.
--
~slg

Sage Gerard

unread,
Feb 28, 2021, 4:21:45 PM2/28/21
to racket...@googlegroups.com

Typo: "but the library itself leverages the reader" should read "but if the library itself leverages the reader"

John Kemp

unread,
Feb 28, 2021, 4:36:29 PM2/28/21
to Ryan Kramer, Racket Users


On Feb 28, 2021, at 2:50 PM, Ryan Kramer <default...@gmail.com> wrote:


[…]


I could use JSON or XML, but that just seems silly when you have a Racket client talking to a Racket server.

Are my concerns founded? Are there any existing solutions? Thanks for any advice.

I don’t think this necessarily answers your question, at least not directly, but receiving code from a remote client is certainly a potential security risk. 

Fortunately, Racket is well-adapted to writing (and parsing) a language (DSL) inside of the language. 

Personally I’m a fan of object capability mechanisms. In Scheme and Racket, some interesting places to start might be 

* Jonathan Rees’ Scheme-based “security kernel” paper: http://mumble.net/~jar/pubs/secureos/secureos.html 
* Marketplace by Tony Garnock-Jones: http://tonyg.github.io/marketplace/

Christoper Lemmer Webber (may be on this list even?) is working on something called Spritely Goblins, an implementation, in Racket, of the CapTP/VatTP protocols that were invented by Mark Miller and others in the E language (http://www.erights.org/elib/capability/ode/ode-capabilities.html) and now being used in Javascript/SES.


And finally, for serializing object (capabilities), the other piece of relevant interesting work is CapnProto by Kenton Varda: https://capnproto.org/

Have fun :)

- johnk 

Ryan Kramer

unread,
Feb 28, 2021, 5:33:12 PM2/28/21
to Racket Users
Thanks everyone. I feel fine to use `read` for this use case now. I overlooked `call-with-default-reading-parameterization` which specifically mentions "reading from untrusted sources" so that is very reassuring.

Philip McGrath

unread,
Mar 5, 2021, 3:08:35 AM3/5/21
to Ryan Kramer, Racket Users
If you don't need the on-the-wire form to be human-readable, you should look at `racket/fasl`: https://docs.racket-lang.org/reference/fasl.html It can handle all of the acyclic data that `read` and `write` can (plus a little extra), it doesn't have `read`'s many configuration parameters that aren't really helpful for plain old data serialization, and it's fast and efficient.

For a human-readable format, `call-with-default-reading-parameterization` is the right answer.

-Philip


Reply all
Reply to author
Forward
0 new messages