Here's a bug that bit sliplogin a while ago. As I mentioned above, it wants to execute an external shell script after the modem line has been configured for serial line IP. The name of the script is fixed at compile time (e.g. /etc/slip-up), so it does this:
pid = fork();
if (pid < 0)
die("fork failed");
if (pid == 0) {
/* child process; run script */
execl("/etc/slip-up", "slip-up", NULL);
exit(1);
}
/* parent, wait for script to complete */
wait(&status);
This looks innocent enough, but...
What happens then is that /bin/sh is started to interpret the script. The shell however recognizes a fairly large number of environment variables, for instance ENV, which specifies a file to be interpreted when the shell starts up. Thus by setting the ENV environment variable to the name of some shell script, an attacker was able to obtain root privilege.3.13
This sort of problem is of course not limited to shell scripts; any other program you execute may also honor environment variables in an unexpected way.
Note that this problem is compounded when you set your real uid to the effective uid before calling the external program. The C library for instance avoids using many ``dangerous'' environment variables when it detects it is running within a setuid process, i.e. when its real uid is different from the effective uid. By making the real uid equal to the effective uid you disable these checks, exposing yourself to many more potentially harmful side effects.
It is therefore important that whenever you execute another program from a setuid application, you should clean up the environment. Be sure that you take a whitelist approach that passes only known harmless variables instead of blacklisting variables that are known to be dangerous - you might miss one that turns out to open a huge hole.
The following code runs the slip-up script with a predefined environment, except for the LANG variable which is copied from the caller's environment. The crucial part here is the use of execve rather than execv or execl. The latter invoke the sub-process with the current environment (which might have been poisoned by the user), while execve takes an array of name=value pairs and passes that as the sub-commands environment.
char langbuf[128], *lang;
char *argv[] = {
"slip-up",
NULL
};
char *env[] = {
"LANG=C",
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
NULL
};
if ((lang = getenv("LANG")) != NULL) {
snprintf(langbuf, sizeof(langbuf),
"LANG=%s", lang);
env[0] = langbuf;
}
pid = fork();
...
if (pid == 0) {
/* child process; run script */
execve("/etc/slip-up", argv, env);
exit(1);
}