SQL injection vulnerabilities have been a plague ever since such databases have been combined with user facing applications. Such vulnerabilities arise when a SQL query string is naively combined with data that is controlled by an attacker.

To mitigate, people should make use of placeholders and prepared statements provided by SQL client libraries. This separates the variable data from the actual query, ensuring that these two never mix. Pretty much all modern SQL client libraries offer this functionality, but of course, it’s still possible to mix variable data and SQL by means of string concatenation.

In the spirit of compile time program validation, I’d like to show a nifty trick that prevents one from using string concatenation to introduce SQL injection vulnerabilities in the software they write using the Rust Language.

Setup

For our setup, let’s hypothesize a function that executes a SQL query. It takes a SQL query and a list of parameter values to be used with placeholders:

fn sql_query ( query : & str , params : & [ & SQLParam ]) -> SQLRows { // ... }

To introduce a SQL injection vulnerability, we could dynamically build a SQL query with the format! macro, Rust’s version of sprintf , like this:

fn get_user_by_name ( username : & str ) { let query = format ! ( "SELECT * FROM users WHERE username={}" , username ); let _rows = sql_query ( & query , & []); // ... }

So this is pretty bad. An attacker could submit a username that interacts with our SQL query. Our program will happily be compiled and the vulnerability can only be discovered by auditing the code.

Let’s make this issue detectable and even let the compiler abort when it encounters this type of vulnerability!

Lifetimes

If you are not yet familiar with Rust, the Language has a concept of lifetimes. These are special conditions that must be met at compile time. They are meant to prevent memory from being freed while there it is still being referenced. Lifetimes are usually confined to the lexical scope of the variables referenced.

Our sql_query ’s query parameter is a reference to the actual string, so to ensure that the referenced memory is not freed while we still reference it, it has lifetime. The lifetime is automatically inferred by the compiler by default. But if we were to add it ourselves as lifetime 'a , our sql_query function would look like this:

fn sql_query < 'a > ( query : & 'a str ) { // ... }

Lifetimes of functions must be declared before they can be used. This is why 'a appears twice; once in the type of query and as declaration in <'a> after the function name.

The ‘static Lifetime

Where am I going with this? Well, there is also a special 'static lifetime for values that must outlive the lifetime of the program.

We can use this to our advantage. If we change the lifetime of query to 'static , it will only accept strings which live as long as the program:

fn sql_query ( query : & 'static str ) { // ... }

This causes dynamic strings to be no longer valid arguments since they are dynamically constructed and can therefore never satisfy 'static .

If we try to compile the faulty example code from earlier, we see that it now fails to compile:

error[E0597]: `query` does not live long enough --> src/main.rs:6:28 | 6 | let _rows = sql_query(&query, &[]); | ^^^^^ borrowed value does not live long enough 7 | } | - borrowed value only lives until here | = note: borrowed value must be valid for the static lifetime...

This means that we are now able to successfully mitigate SQL injection vulnerabilities without even running the program!

To fix, we must of course provide a static string and use the placeholder ? for the username.

let _rows = sql_query ( "SELECT * FROM users WHERE username=?" , & [ username ]);

Because the query string is now a string literal it satisfies the 'static lifetime, allowing the program to compile.

Conclusion

Rust is a language with many interesting features which aid programmers to write code that can be statically checked for correctness. This also gives it a pretty steep learning curve and consequently, those that master this language are probably significantly less likely to commit mistakes like SQL injections.

But this is yet another demonstration of the power of static program analysis and how it helps us all to create better and more secure software.

Someone has brought the Box::leak to my attention. This function, recently introduced in Rust 1.26, allows one to produce strings with the 'static runtime by never freeing the allocated memory.

Although it becomes possible to circumvent the trick presented in this article, I do not believe it is less useful because its real advantage is that it still makes it harder to do the wrong thing. Something that encourages the programmer to find a better alternative.