-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Dumping a string as properly escaped Crystal string literal #9953
Comments
What about a string literal that is immune to any interpretation, wouldn't that solve all these usecases? Something like Of course to then interpolate values you would have to use concatenation or |
As a workaround for now I guess you could wrap the generated code into |
This is just String.inspect, and it should simply be fixed to be safe against macros |
inspect is made to be specific to Crystal, though |
So @oprypin Yeah, that's actually a good point 🤔 🤔 I'd really like inspect to return what could be used as crystal code to recreate that exact value. At least where that's possible, primitives, simple data structures. Unfortunately #7525 has lead us in a different direction 😞 But with this use case in mind, it actually fits perfectly I think. Just add macro expression escapes. |
Can you provide an example of code injection? I also don't understand how this can happen given that macros are compile time things. Security concerns with code injection only happen at runtime. |
Generally: security vulnerabilities always find a way. Someone will see how to exploit it even if you don't see right now. Using Crystal compiler & source code as an implementation detail of some service turns this into a run-time injection. Silly example: there's an online service that lets you download a binary which only prints the text that you supplied to it. It happens to be implemented by creating source code |
I don't think @straight-shoota's motivation here is runtime security though, but code generation safety. |
But macro code isn't executed inside string literals... |
Hmmmm seems so :o |
Both security and safety are a concern here.
Say I'd run a service that provides ready-built custom hello world programs. Users can enter a name to be greeted and the service then compiles a binary with the user-provided name embedded. Obviously, it uses Crystal as a programming language because it's simply the best for this kind of software ;) When the user-provided name contains unescaped macro expressions, it could be used for code injections. A name like Macro expressions in string literals do work inside a macro block: {% begin %}
puts "{{`echo ok`}}" # => ok
{% end %} |
Yes, but how can an string passed by a user get into that macro. |
Oh, I just read it. If you provide user input to a program, I think you have other concerns too, like using a sandbox, etc. At that point, having a library or something that escapes that code sounds good. I'm not sure it belongs to the std. That said, I wouldn't mind a macro_escape method for String, but we'd have to be careful about explaining when to use it. |
Plus you already have this implemented. I just don't think this is a common use case, to be honest. |
There isn't actually much that needs to change. I believe we can agree that the return value of
I'd actually prefer the latter. I don't think it's useful to have macro expansion in literals, it can certainly be surprising. It's also not consistent because it doesn't even work outside of macro bodies (so perhaps it's just a mistake that it works inside?). If you need to expand a macro expression inside a string literal, you can just wrap it in string interpolation. The compiler could even resolve that interpolation at compiler time, so that Furthermore we should also clarify the use case and behaviour of |
The spec command generates Crystal source code looking like this (simplified):
%(require "./#{path}")
.path
is an arbitrary path and may include characters or combinations of characters that lead to the resulting code being either illegal or messed up (for example if it contains"
,#{
or{{
).Some problems can be fixed using
String#dump
. That would escape double quotes, string interpolation and any illegal characters. But it does not excape macro expressions. This could theoretically offer an oportunity for code injection. For the spec runner that's maybe not a relevant threat, but would still cause issues when you're working with funny paths.I'm not aware of any solutions in the compiler or stdlib that make this safe to work. Except appling manual gsubs which are prone to being incomplete.
(This specific use case could probably also make use of Compiler access to construct the respective AST nodes programmatically instead of letting the parser do that. This should avoid problems with string literal transformation.)
I've faced this problem before with
baked_file_system
. Since then it has a specific encoder which should make sure the generated string literals are sane and don't try to expand macro expressions: https://github.com/schovi/baked_file_system/blob/master/src/loader/string_encoder.crThere surely are other cases as well, either in the compiler or in user code (
macro run
programs would come to mind) where a string is supposed to be written as a string literal in Crystal code and not cause any unexpected effect.Maybe we could add handling for macro delimiters to
String#dump
. But I don't think that's good. I it my understanding thatString#dump
is a pretty generic method to escape a string using common escape sequences and not specific to Crystal syntax. So even escaped interpolation might be unexpected behaviour when you're using this method for other context than Crystal code (or Ruby which has the same syntax).So perhaps it would be better to have a separate method specifcally for escaping a Crystal string literal. Or it could be a flag on
String#dump
. I'm not sure what's the best API.Comments?
The text was updated successfully, but these errors were encountered: