A process inherits its environment variables from its parent process. While the parent process most often will not do anything to tarnish the environment passed on to its children, your program's environment variables are still external inputs, and you must therefore treat them as such.
The process that parents your own process could be a malicious process that has manipulated the environment in an attempt to confuse your program and exploit that confusion to nefarious ends. As much as possible, it is best to avoid depending on the environment, but we recognize that is not always possible.
In the following subsections, we'll look at obtaining the value of an environment variable as well as changing and deleting environment variables.
The
normal means by which you obtain the value of an environment variable
is by calling getenv( )
with the name of the environment variable
whose value is to be retrieved. The problem with getenv(
)
is that it simply returns a pointer into the environment,
rather than returning a copy of the environment
variable's value.
If you do not immediately make a copy of the value returned by
getenv( )
, but instead store the pointer somewhere
for later use, you could end up with a dangling pointer or a
different value altogether, if the environment is modified between
the time that you called getenv( )
and the time
you use the pointer it returns.
There is a race condition here even after you call
getenv()
and before you copy. Be careful to only
manipulate the process environment from a single thread at a time.
Never make any assumptions about the length or the contents of an environment variable's value. It can be extremely dangerous to simply copy the value into a statically allocated buffer or even a dynamically allocated buffer that was not allocated based on the actual size of the environment variable's value. Always compute the size of the environment variable's value yourself, and dynamically allocate a buffer to hold the copy.
Another problem with environment variables is that a malicious
program could manipulate the environment so that two or more
environment variables with the same name exist in your
process's environment. It is easy to detect this
situation, but it usually is not worth concerning yourself with it.
Most, if not all, implementations of getenv( )
will always return the first occurrence of an environment variable.
As a convenience, you can use the function spc_getenv(
)
,
shown in the following code, to obtain the value of an environment
variable. It will return a copy of the environment
variable's value allocated with strdup(
)
, which
means that you will be responsible for freeing the memory with
free( )
.
#include <stdlib.h> #include <string.h> char *spc_getenv(const char *name) { char *value; if (!(value = getenv(name))) return 0; return strdup(value); }
The standard C runtime function
putenv( )
is normally used to modify the value of an
environment variable. In some implementations, putenv(
)
can even be used to delete environment variables, but
this behavior is nonstandard and therefore is not portable. If you
have sanitized the environment as described in Recipe 1.1, and
particularly if you use the code in that recipe, using
putenv( )
could cause problems because of the way
that code manages the memory allocated to the environment. We
recommend that you avoid using the putenv( )
function altogether.
Another reason to avoid putenv( )
is that an
attacker could have manipulated the environment before spawning your
process, in such a way that two or more environment variables share
the same name. You want to make certain that changing the value of an
environment variable actually changes it. If you use the code from
Recipe 1.1, you can be reasonably certain that there is only one
environment variable for each name.
Instead of using putenv( )
to modify the value of
an environment variable, use spc_putenv(
)
,
shown in the following code. It will properly handle an environment
as the code in Recipe 1.1 builds it, as well as an unaltered
environment. In addition to modifying the value of an environment
variable, spc_putenv( )
is also capable of adding
new environment variables.
We have not copied putenv( )
's
signature with spc_putenv( )
. If you use
putenv( )
, you must pass it a string of the form
"NAME=VALUE". If you use
spc_putenv( )
, you must pass it two strings; the
first string is the name of the environment variable to modify or
add, and the second is the value to assign to the environment
variable. If an error occurs, spc_putenv( )
will
return -1; otherwise, it will return 0.
Note that the following code is not thread-safe. You need to explicitly avoid the possibility of manipulating the environment from two separate threads at the same time.
#include <stdlib.h> #include <string.h> static int spc_environ; int spc_putenv(const char *name, const char *value) { int del = 0, envc, i, mod = -1; char *envptr, **new_environ; size_t delsz = 0, envsz = 0, namelen, valuelen; extern char **environ; /* First compute the amount of memory required for the new environment */ namelen = strlen(name); valuelen = strlen(value); for (envc = 0; environ[envc]; envc++) { if (!strncmp(environ[envc], name, namelen) && environ[envc][namelen] = = '=') { if (mod = = -1) mod = envc; else { del++; delsz += strlen(environ[envc]) + 1; } } envsz += strlen(environ[envc]) + 1; } if (mod = = -1) { envc++; envsz += (namelen + valuelen + 1 + 1); } envc -= del; /* account for duplicate entries of the same name */ envsz -= delsz; /* allocate memory for the new environment */ envsz += (sizeof(char *) * (envc + 1)); if (!(new_environ = (char **)malloc(envsz))) return 0; envptr = (char *)new_environ + (sizeof(char *) * (envc + 1)); /* copy the old environment into the new environment, replacing the named * environment variable if it already exists; otherwise, add it at the end. */ for (envc = i = 0; environ[envc]; envc++) { if (del && !strncmp(environ[envc], name, namelen) && environ[envc][namelen] = = '=') continue; new_environ[i++] = envptr; if (envc != mod) { envsz = strlen(environ[envc]); memcpy(envptr, environ[envc], envsz + 1); envptr += (envsz + 1); } else { memcpy(envptr, name, namelen); memcpy(envptr + namelen + 1, value, valuelen); envptr[namelen] = '='; envptr[namelen + valuelen + 1] = 0; envptr += (namelen + valuelen + 1 + 1); } } if (mod = = -1) { new_environ[i++] = envptr; memcpy(envptr, name, namelen); memcpy(envptr + namelen + 1, value, valuelen); envptr[namelen] = '='; envptr[namelen + valuelen + 1] = 0; } new_environ[i] = 0; /* possibly free the old environment, then replace it with the new one */ if (spc_environ) free(environ); environ = new_environ; spc_environ = 1; return 1; }
No
method for deleting an environment variable is defined in any
standard. Some implementations of putenv(
)
will
delete environment variables if the assigned value is a zero-length
string. Other systems provide implementations of a function called
unsetenv( )
, but it is nonstandard and thus nonportable.
None of these methods of deleting environment variables take into
account the possibility that multiple occurrences of the same
environment variable may exist in the environment. Usually, only the
first occurrence will be deleted, rather than all of them. The result
is that the environment variable won't actually be
deleted because getenv( )
will return the next
occurrence of the environment variable.
Especially if you use the code from Recipe 1.1 to sanitize the
environment, or if you use the code from the previous subsection, you
should use spc_delenv(
)
to
delete an environment variable. The following code for
spc_delenv( )
depends on the static variable
spc_environ
declared at global scope in the
spc_putenv( )
code from the previous subsection;
the two functions should share the same instance of that variable.
Note that the following code is not thread-safe. You need to explicitly avoid the possibility of manipulating the environment from two separate threads at the same time.
#include <stdlib.h> #include <string.h> int spc_delenv(const char *name) { int del = 0, envc, i, idx = -1; size_t delsz = 0, envsz = 0, namelen; char *envptr, **new_environ; extern int spc_environ; extern char **environ; /* first compute the size of the new environment */ namelen = strlen(name); for (envc = 0; environ[envc]; envc++) { if (!strncmp(environ[envc], name, namelen) && environ[envc][namelen] = = '=') { if (idx = = -1) idx = envc; else { del++; delsz += strlen(environ[envc]) + 1; } } envsz += strlen(environ[envc]) + 1; } if (idx = = -1) return 1; envc -= del; /* account for duplicate entries of the same name */ envsz -= delsz; /* allocate memory for the new environment */ envsz += (sizeof(char *) * (envc + 1)); if (!(new_environ = (char **)malloc(envsz))) return 0; envptr = (char *)new_environ + (sizeof(char *) * (envc + 1)); /* copy the old environment into the new environment, ignoring any * occurrences of the environment variable that we want to delete. */ for (envc = i = 0; environ[envc]; envc++) { if (envc = = idx || (del && !strncmp(environ[envc], name, namelen) && environ[envc][namelen] = = '=')) continue; new_environ[i++] = envptr; envsz = strlen(environ[envc]); memcpy(envptr, environ[envc], envsz + 1); envptr += (envsz + 1); } /* possibly free the old environment, then replace it with the new one */ if (spc_environ) free(environ); environ = new_environ; spc_environ = 1; return 1; }