This post contains a short proof-of-concept for a technique which allows library authors to implement injection-proof interfaces. It combines two techniques used by Bryan O’Sullivan in the mysql-simple and text packages:
Wrap injection-vulnerable string parameters in a new type with a restricted API.
Use GHC’s rewriting facilities to modify the code generated for literal values of string types.
This technique (which probably isn’t new, but is new to me) works like this:
Create a new type which wraps your problematic strings.
Define an instance of
IsStringfor this new type.Implement a new “unsafe” version for the
fromStringmethod.Use a rule to specialise
fromStringto yourfromStringUnsafefunction.Define a “safe” version of the
fromStringmethod.Use a rule to replace “safe” calls to
fromStringUnsafewith yourfromStringSafefunction.
The first two steps result in an API in which writing unsafe code requires explicit effort from the programmer. Rather than writing:
result <- query_ $ "SELECT * FROM table WHERE id = " ++ theId
a programmer will need to write:
result <- query_ $ fromString $ "SELECT * FROM table WHERE id = " ++ theId
This is certainly an improvement, but still allows unsafe operations to be performed.
Steps three and four convince GHC to replace the fromString calls which
convert string values (be they literal as in the first example or dynamic as
in the second) into our custom type with calls to our own specialised
function. Functionally, this should have no impact on the program.
In steps five and six we use a technique from text, where we use knowledge
of GHC’s internals to rewrite the code which handles our string literals and
replace this code with something a little more specialised. In the text
package, this results in code which goes straight from GHC.CString.Addr# to
Text values rather than going via an intermediate String.
So far this seems like a little bit of security theatre with a little bit of
optimisation, but it becomes more powerful when we make our implementations of
fromString and fromStringUnsafe call error and only fromStringSafe
actually create a Query (or whatever your type is).