2011-07-22
String expansion and securely running programs on Unix
One of the corollaries of how to securely run programs on Unix is that a general purpose, generic string expansion system is a bad fit with securely running programs. The problem is that there is a fundamental clash of goals between the two systems: a generic string expansion system wants to treat everything as a generic string to be expanded (regardless of what it actually is), and a secure system for running programs wants to tokenize everything using simple rules.
At this point I am going to pick on Exim for illustrative examples. Unfortunately, Exim tries to have it both ways at once and thus is a great source for showing the problems that this causes, no matter how much I like it otherwise. Please note that the problems here are generic; any program that takes either approach (or both at once as Exim does) will have the same issues.
First up is Exim's av_scanner
setting. This is not expanded at all
unless it starts with a '$', at which point the entire string must
be expanded before Exim knows how to tokenize it:
av_scanner = ${if bool{true} {cmdline:/opt/avscanner $recipients %s}}
If you are concerned about arbitrary characters appearing in
$recipients
, there is no way to make this secure (as discussed
before).
Second, the command
setting for running things in pipes. This
tokenizes things before string expansion, but it does the tokenization
purely on a textual basis. As the documentation notes, this causes
serious problems:
command = /some/path ${if eq{$local_part}{postmaster} {xx} {yy}}
Since tokenization is expansion-blind, this fails because all the string
expansion evaluator winds up seeing is '${if
' (which is a clear syntax
error). To get this to work you have to force the tokenizer to treat the
entire string expansion as a single token by 'quoting' it.
(The documentation does not quite put it the way that I have here.)
A side effect of tokenization before expansion is that a single string
expansion can only ever expand to a single argument. (You may or may not
be able to expand to nothing instead of a ''
empty argument, depending
on the implementation.)
What this points out is that command line tokenization and string
expansion need to be aware of each other. Once the dust settles,
either string expansion needs to be able to mark hard token boundaries
(so that $recipients
can be marked as a single token regardless of
contents) or tokenization needs to know about the string expansion
language (so that ${if ...}
can be parsed into a single token
despite the presence of internal spaces or other special characters).
(I have opinions on the answer here, but this entry is already long enough as it is.)
PS: if you want to be secure with minimal effort, it's clear that you need to do tokenization before expansion and provide some sort of 'quoting' mechanism to glue a string expansion expression into a single token. This is secure while being merely inconvenient and annoying to people writing configuration files. Simple expansion before tokenization cannot be made secure at all, as previously discussed.