|
/************************************** * * 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"); }