How to securely run programs from inside your program on Unix

June 28, 2011

Every so often, people feel that their program needs to be able to run another program under some circumstances; for instance, you're writing a mailer and you want to be able to run an external virus scanner to see if a newly received message has a virus. A certain amount of these people decide to use system() or popen() for this; as the saying goes, now they have two problems.

(The only time it's ever okay to use these routines is if the entire command line to run is set statically in your configuration file, with no runtime expansion, substitution, or insertion of anything at all.)

Hopefully everyone understands why: both of these routines pass your command line to the shell to have it interpret the line (that's why they can take a 'command line' as an argument, instead of an array of command arguments). This means that any shell metacharacters that appear in substitutions or expansions will be interpreted by the shell, generally with disastrous results. So the first rule of securely running other programs is that you must execute programs yourself, using an interface (such as the exec*() family) that lets you directly control what the command line arguments are.

This means that you're responsible for breaking up your 'command line' into command line arguments, and as it happens there's a right and a wrong way to do this. The right way is to tokenize your command line before you perform substitutions.

Suppose that you have a hypothetical command line:

/opt/scanner $spoolfile $sender $recipient

If you're directly running /opt/scanner, this looks safe; it will get called with three command line arguments and will be happy. But suppose that someone manages to sneak a space into $sender. If you expand the variables in this command line before breaking it up into arguments, the scanner is suddenly getting called with four arguments, which is not good. (And even without that it could be called with two arguments if $sender is sometimes allowed to be empty.)

The proper way to do this is to break this template command line up into arguments, then expand each argument separately. No extra arguments will be created no matter what spaces or other characters are introduced by the expansion, and if an expansion is empty you won't delete arguments (the scanner will instead see an empty $sender argument). This is exactly what you want to happen.

(My off the cuff instinct is that tokenization before expansion is in fact what you want for general string substitutions, but I haven't thought deeply about the issue. It certainly is what you want in this specific situation.)

Some people will say that they really do need the shell because they need to do things like redirection and substituting shell variables. The right answer is for these people to write a little shell script around their real program, not for your program to use the shell to run everything. A similar answer applies to people who need string substitution to retokenize. In both cases the principle is that if you have security you can always give it away, but if you don't have security to start with you cannot get it back.

(If people often need redirection or shell variables or retokenization after substitution, you have a deeper design issue. Find out why they keep needing to do these things in your system and fix it so that they don't.)

Written on 28 June 2011.
« Formatting information output to make it easy to manage
Our ZFS spares handling system (part 1) »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Jun 28 00:23:18 2011
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.