3.6 Using Environment Variables Securely

3.6.1 Problem

You need to obtain the value of, alter the value of, or delete an environment variable.

3.6.2 Solution

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.

3.6.3 Discussion

In the following subsections, we'll look at obtaining the value of an environment variable as well as changing and deleting environment variables.

3.6.3.1 Obtaining the value of an environment variable

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);
}
3.6.3.2 Changing the value of an environment variable

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;
}
3.6.3.3 Deleting an environment variable

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;
}

3.6.4 See Also

Recipe 1.1