TUCoPS :: Linux :: Discontinued :: envcheck.c

envcheck 1.3 LKM Envcheck is a Linux kernel module which detects and prevents exploitation of the recent glibc vulnerabilities by intercepting the execve system call and sanitising the enviroment passed. At the cost of a very small performance penalty, it has advantages over a glibc upgrade, including logging of exploit attempts, it works with statically linked binaries, it is transparent to applications that may be sensitive to a change of glibc, and it partially protects libc5.

/**************************************
 *
 * envcheck.c
 *
 * Written by Germán Cancio, Lionel Cons & Jan Iven
 * (C) CERN http://www.cern.ch
 *
 * $Id: envcheck.c,v 1.3 2000/09/11 12:22:26 cons Exp $
 *
 * Note: this is supposed to work only on i386 kernels
 */

/**************************************
 *
 * header defs etc
 *
 */

#define MODULE
#define __KERNEL__

#include <linux/config.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sys.h>
#include <linux/smp_lock.h>

#include <asm/uaccess.h>
#include <sys/syscall.h>

/**************************************
 *
 * module parameters and defaults
 *
 */

/* unloadable */
static int unloadable = 0;

/* verbose: log changes made to the environment */
static int verbose = 1;

/* maxlog: data longer than this will be truncated and [...] will be appended */
static int maxlog = 64;

/* maxenv: maximum number of environment variables allowed */
static int maxenv = 1024;

MODULE_AUTHOR("CERN IT/PDP/OSE");
MODULE_DESCRIPTION("environment check");
MODULE_PARM(unloadable, "i");
MODULE_PARM(verbose, "i");
MODULE_PARM(maxlog, "i");
MODULE_PARM(maxenv, "i");

/* the system calls vector table */
extern void *sys_call_table[];

/* pointer to the original execve system call */
static int (*old_execve) (const char *, const char *[], const char *[]);

/* maximum allowed size for an environment variable */
static const int MAX_ENV_SIZE = PAGE_SIZE >> 1;

/* locale related environment variables (length is without the final \0) */
static char *env_lang[] = {
  "LANG=",
  "LANGUAGE=",
  "LC_ALL=",
  "LC_COLLATE=",
  "LC_CTYPE=",
  "LC_MESSAGES=",
  "LC_MONETARY=",
  "LC_NUMERIC=",
  "LC_TIME=",
  NULL
};  
static int env_lang_len[] = {
  5, 9, 7, 11, 9, 12, 12, 11, 8, 0
};

/********************************
 *
 * sanitise a buffer: remove non-printable characters and truncate
 *
 */
static void sanitise(char *buffer)
{
  char *cp;
  int count;

  for (cp=buffer,count=0; *cp; cp++,count++) {
    if (*cp < ' ' || *cp > '~') *cp = '.';
    if (count >= maxlog) {
      *cp++ = '[';
      *cp++ = '.';
      *cp++ = '.';
      *cp++ = '.';
      *cp++ = ']';
      *cp++ = 0x00;
      break;
    }
  }
}

/********************************
 *
 * replacement for the original execve system call
 *
 */
asmlinkage int new_execve(struct pt_regs regs)
{
  char *filename, *string1, *string2;
  char **user_env;
  int error, i, j;
  long len;
  char *user_env_string1, *user_env_string2, *equal;
  
  /* root has the right to shoot himself in the foot */
  if (current->uid == 0) goto normal_processing;
  
  /* allocate buffers to hold the strings that we manipulate */
  string1 = (char*)kmalloc(MAX_ENV_SIZE<<1, GFP_KERNEL);
  string2 = string1 + MAX_ENV_SIZE;
  
  /*
   * check the environment
   */
  user_env = (char **)regs.edx;
#define DISCARDED_MARKER ((char*)user_env)
  for (i=0; ; i++) {
    /* check that we don't have too many variables */
    if (i >= maxenv) break;
    /* copy the string pointer to kernel space */
    get_user(user_env_string1, &user_env[i]);
    /* check for end of environment */
    if (user_env_string1 == NULL) break;
    /* check for discarded variable */
    if (user_env_string1 == DISCARDED_MARKER) continue;
    /* copy the string to kernel space */
    len = strncpy_from_user(string1, user_env_string1, MAX_ENV_SIZE);
    if (len < 0) {
      if (verbose)
	printk(KERN_INFO "envcheck: %.30s (uid=%d) discarded bogus variable\n",
	       current->comm, current->uid);
      put_user(DISCARDED_MARKER, &user_env[i]);
      continue;
    }
    /* check for variable too big */
    if (len >= MAX_ENV_SIZE) {
      if (verbose)
	printk(KERN_INFO "envcheck: %.30s (uid=%d) discarded variable too big\n",
	       current->comm, current->uid);
      put_user(DISCARDED_MARKER, &user_env[i]);
      continue;
    }
    /* check for equal sign */
    equal = strchr(string1, '=');
    if (!equal) {
      if (verbose) {
	sanitise(string1);
	printk(KERN_INFO "envcheck: %.30s (uid=%d) discarded variable without =: %s\n",
	       current->comm, current->uid, string1);
      }
      put_user(DISCARDED_MARKER, &user_env[i]);
      continue;
    }
    /*
     * check locale related variables but only for L[AC]* (speed optimisation)
     */
    if (string1[0] != 'L') goto duplicates_testing;
    if (string1[1] != 'A' && string1[1] != 'C') goto duplicates_testing;
    for (j=0; env_lang[j] != NULL; j++) {
      /* too short */
      if (len <= env_lang_len[j]) continue;
      /* don't match */
      if (strncmp(env_lang[j], string1, env_lang_len[j])) continue;
      /* check for a / in the value part */
      if (strchr(&string1[env_lang_len[j]], '/')) {
	if (verbose) {
	  sanitise(string1);
	  printk(KERN_INFO "envcheck: %.30s (uid=%d) discarded locale variable with /: %s\n",
		 current->comm, current->uid, string1);
	}
	put_user(DISCARDED_MARKER, &user_env[i]);
      }
      break;
    }
  duplicates_testing:
    /*
     * check duplicate variables but only for LD_* (speed optimisation)
     */
    if (string1[0] != 'L') continue;
    if (string1[1] != 'D') continue;
    if (string1[2] != '_') continue;
    for (j=i+1; ; j++) {
      /* check that we don't have too many variables */
      if (j >= maxenv) break;
      /* copy the string pointer to kernel space */
      get_user(user_env_string2, &user_env[j]);
      /* check for end of environment */
      if (user_env_string2 == NULL) break;
      /* check for discarded variable */
      if (user_env_string2 == DISCARDED_MARKER) continue;
      /* copy the string to kernel space */
      len = strncpy_from_user(string2, user_env_string2, MAX_ENV_SIZE);
      if (len < 0) {
	if (verbose)
	  printk(KERN_INFO "envcheck: %.30s (uid=%d) discarded bogus variable\n",
		 current->comm, current->uid);
	put_user(DISCARDED_MARKER, &user_env[j]);
	continue;
      }
      /* check for variable too big */
      if (len >= MAX_ENV_SIZE) {
	if (verbose)
	  printk(KERN_INFO "envcheck: %.30s (uid=%d) discarded variable too big\n",
		 current->comm, current->uid);
	put_user(DISCARDED_MARKER, &user_env[j]);
	continue;
      }
      /* check if it's duplicate */
      if (!strncmp(string1, string2, equal-string1+1)) {
	if (verbose) {
	  sanitise(string2);
	  printk(KERN_INFO "envcheck: %.30s (uid=%d) discarded duplicate variable: %s\n",
		 current->comm, current->uid, string2);
	}
	put_user(DISCARDED_MARKER, &user_env[j]);
      }
    }
  }
  
  /*
   * cleanup: we really discard the buggy things
   */
  for (i=j=0; ; i++) {
    /* check that we don't have too many variables and truncate if needed */
    if (i >= maxenv) {
      if (verbose)
	printk(KERN_INFO "envcheck: %.30s (uid=%d) too many environment variables: %d\n",
	       current->comm, current->uid, i);
      put_user(NULL, &user_env[i]);
      break;
    }
    /* copy the string pointer to kernel space */
    get_user(user_env_string1, &user_env[i]);
    /* check for end of environment */
    if (user_env_string1 == NULL) break;
    /* check for discarded variable */
    if (user_env_string1 == DISCARDED_MARKER) continue;
    /* this entry is kept, move it if needed */
    if (j < i) put_user(user_env_string1, &user_env[j]);
    /* one more valid entry seen */
    j++;
  }
  /* copy the finall NULL if needed */
  if (j+1 < i) put_user(NULL, &user_env[j]);
  /* free the buffers that we allocated */
  kfree(string1);

normal_processing:
  /*
   * this comes straight from arch/i386/kernel/process.c
   * do_execve() will also check for us that the pointers are all correct
   */
  lock_kernel();
  filename = getname((char *) regs.ebx);
  error = PTR_ERR(filename);
  if (IS_ERR(filename))
    goto out;
  error = do_execve(filename, (char **) regs.ecx, (char**) regs.edx, &regs);
  if (error == 0)
    current->flags &= ~PF_DTRACE;
  putname(filename);
out:
  unlock_kernel();
  return error;
}

/**************************************
 *
 * initialize module
 *
 */
int init_module(void) {

  /* enforce a hard limit */
  if (maxlog > 512) maxlog = 512;
  if (maxlog > MAX_ENV_SIZE-6) maxlog = MAX_ENV_SIZE-6; /* protect sanitise() */

  /* cannot be unloaded? */
  if (unloadable) {
    MOD_INC_USE_COUNT;
  }

  /* do not export symbols */
  EXPORT_NO_SYMBOLS;

  /* redirect execve */
  old_execve = sys_call_table[SYS_execve];
  sys_call_table[SYS_execve] = (void*)new_execve;

  printk(KERN_INFO "envcheck: loaded: %sremovable, maxlog %d, maxenv %d, %sverbose\n",
	 (unloadable ? "not " : ""), maxlog, maxenv, (verbose ? "" : "not "));
  return(0);
}

/**************************************
 *
 * cleanup module
 *
 */
void cleanup_module(void) {

  /* restore execve */
  sys_call_table[SYS_execve] = (void*)old_execve;

  printk(KERN_INFO "envcheck: unloaded\n");
}

TUCoPS is optimized to look best in Firefox® on a widescreen monitor (1440x900 or better).
Site design & layout copyright © 1986-2024 AOH