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, ®s);
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-2025 AOH