diff --git a/Makefile b/Makefile index 6192922..bdec579 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ VERSION = 2 PATCHLEVEL = 6 SUBLEVEL = 26 -EXTRAVERSION = +EXTRAVERSION = -pH NAME = Rotary Wombat # *DOCUMENTATION* diff --git a/arch/x86/kernel/entry_32.S b/arch/x86/kernel/entry_32.S index 6bc07f0..4621d69 100644 --- a/arch/x86/kernel/entry_32.S +++ b/arch/x86/kernel/entry_32.S @@ -336,6 +336,15 @@ sysenter_past_esp: jnz syscall_trace_entry cmpl $(nr_syscalls), %eax jae syscall_badsys +#ifdef CONFIG_SECURITY_PH + /* + * pH: process the current system call + * note that this gets skipped when processed is + * ptraced (maybe this is good?) + */ + call pH_process_syscall_i386 + movl PT_EAX(%esp),%eax +#endif /* CONFIG_SECURITY_PH */ call *sys_call_table(,%eax,4) movl %eax,PT_EAX(%esp) LOCKDEP_SYS_EXIT diff --git a/arch/x86/kernel/process_32.c b/arch/x86/kernel/process_32.c index 0c3927a..8a2bb1b 100644 --- a/arch/x86/kernel/process_32.c +++ b/arch/x86/kernel/process_32.c @@ -727,3 +727,18 @@ unsigned long arch_randomize_brk(struct mm_struct *mm) unsigned long range_end = mm->brk + 0x02000000; return randomize_range(mm->brk, range_end, 0) ? : mm->brk; } + +#ifdef CONFIG_SECURITY_PH +asmlinkage void pH_process_syscall_i386(struct pt_regs regs) +{ + pH_process_syscall(regs.ax); +} + +asmlinkage int sys_pH(struct pt_regs regs) +{ + int result; + /* pass option flag and buffer pointer */ + result = do_pH((int) regs.bx, (int) regs.cx); + return result; +} +#endif /* CONFIG_SECURITY_PH */ diff --git a/arch/x86/kernel/syscall_table_32.S b/arch/x86/kernel/syscall_table_32.S index adff556..1cccb40 100644 --- a/arch/x86/kernel/syscall_table_32.S +++ b/arch/x86/kernel/syscall_table_32.S @@ -221,7 +221,11 @@ ENTRY(sys_call_table) .long sys_madvise .long sys_getdents64 /* 220 */ .long sys_fcntl64 - .long sys_ni_syscall /* reserved for TUX */ +#ifdef CONFIG_SECURITY_PH + .long sys_pH /* stolen from TUX */ +#else + .long sys_ni_syscall /* reserved for TUX */ +#endif .long sys_ni_syscall .long sys_gettid .long sys_readahead /* 225 */ diff --git a/fs/exec.c b/fs/exec.c index fd92343..5d0c7b1 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -60,6 +60,10 @@ #include #endif +#ifdef CONFIG_SECURITY_PH +#include +#endif + #ifdef __alpha__ /* for /sbin/loader handling in search_binary_handler() */ #include @@ -1284,6 +1288,10 @@ int do_execve(char * filename, if (!bprm) goto out_files; +#ifdef CONFIG_SECURITY_PH + pH_do_suspend_execve(); +#endif + file = open_exec(filename); retval = PTR_ERR(file); if (IS_ERR(file)) @@ -1336,6 +1344,9 @@ int do_execve(char * filename, free_bprm(bprm); if (displaced) put_files_struct(displaced); +#ifdef CONFIG_SECURITY_PH + pH_execve(file); +#endif return retval; } diff --git a/fs/nfs/super.c b/fs/nfs/super.c index 1b94e36..07bcc09 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -1718,9 +1718,14 @@ nfs_remount(struct super_block *sb, int *flags, char *raw_data) * ones were explicitly specified. Fall back to legacy behavior and * just return success. */ - if ((nfsvers == 4 && options4->version == 1) || - (nfsvers <= 3 && options->version >= 1 && - options->version <= 6)) + + /* + * Patch by Marc Zyngier + * http://marc.info/?l=linux-kernel&m=121630235226924&w=2 + */ + if ((nfsvers == 4 && (!options4 || options4->version == 1)) || + (nfsvers <= 3 && (!options || (options->version >= 1 && + options->version <= 6)))) return 0; data = kzalloc(sizeof(*data), GFP_KERNEL); diff --git a/include/asm-x86/unistd_32.h b/include/asm-x86/unistd_32.h index 8317d94..dbd8beb 100644 --- a/include/asm-x86/unistd_32.h +++ b/include/asm-x86/unistd_32.h @@ -228,6 +228,11 @@ #define __NR_madvise1 219 /* delete when C lib stub is removed */ #define __NR_getdents64 220 #define __NR_fcntl64 221 +#ifdef CONFIG_SECURITY_PH +#define __NR_pH 222 +#else +/* 222 is unused */ +#endif /* 223 is unused */ #define __NR_gettid 224 #define __NR_readahead 225 diff --git a/include/linux/pH.h b/include/linux/pH.h new file mode 100644 index 0000000..67a3cd0 --- /dev/null +++ b/include/linux/pH.h @@ -0,0 +1,200 @@ +/* + * linux/include/linux/pH.h + * + * Copyright (C) 1999-2003 Anil Somayaji + * + * Header file for pH, a kernel extension for online process + * intrusion detection monitoring and response. + * + * Licensed under the GNU GPL (see COPYING file), and as such has NO + * WARRANTY! In particular, pH modifies the filesystem using + * low-level access routines. Have a good backup, you could lose + * data through disk corruption! + * + * August 16, 2000: pH-0.1 initial release + * July 17, 2001: pH-0.17 + * September 9, 2001: pH-0.18 final version for my dissertation + * + * June 5, 2003: pH-0.22 first public release of version for 2.4 + * + * $Id: pH.h,v 1.4 2003/06/05 21:00:23 absomay Exp $ */ + +#ifndef __KERNEL__ +#define __KERNEL__ +#endif + +#include +#include +#include +#include +#include + +#ifndef _LINUX_PH_H +#define _LINUX_PH_H + +#define PH_NUM_SYSCALLS 256 /* size of array */ +#define PH_EMPTY_SYSCALL 255 /* note: this value is used as the */ + /* "no system call" marker in sequences */ +#define PH_COUNT_PAGE_MAX (PAGE_SIZE / PH_NUM_SYSCALLS) +#define PH_MAX_PAGES (PH_NUM_SYSCALLS / PH_COUNT_PAGE_MAX) + +#define PH_MIN_SEQLEN 2 +#define PH_MAX_SEQLEN 9 +#define PH_MAX_FILENAME PAGE_SIZE +#define PH_MAX_DISK_FILENAME 256 +#define PH_LOCALITY_WIN 128 +#define PH_FILE_MAGIC_LEN 20 + +#define PH_STOPMON 0 +#define PH_STARTMON 1 +#define PH_DELAYFACTOR 2 +#define PH_MODMIN 3 +#define PH_NORMALMIN 4 +#define PH_TOLERIZE_LIMIT 5 +#define PH_ANOM_LIMIT 6 +#define PH_NORMFACTOR 7 +#define PH_RESET_PROFILE 8 +#define PH_TOLERIZE 9 +#define PH_SENSITIZE 10 +#define PH_STATUS 11 +#define PH_LOGLEVEL 12 +#define PH_LOG_SYSCALLS 13 +#define PH_START_NORMAL 14 +#define PH_LOG_SEQUENCES 15 +#define PH_WRITE_PROFILES 16 +#define PH_SUSPEND_EXECVE 17 +#define PH_GETMSG 18 +#define PH_STARTSIGNAL 19 +#define PH_STOPSIGNAL 20 +#define PH_NORMAL_WAIT 23 +#define PH_SUSPEND_EXECVE_TIME 24 +#define PH_RESET_LFC 25 + +/* These operations have been deleted, but leave the defs here + so we don't reuse them. */ +#define PH_FREQ 19 +#define PH_FREQ_LOG 20 +#define PH_FREQ_DELAY 21 +#define PH_FREQ_INTERVAL 22 + +#define PH_LOG_ERR 1 /* real errors */ +#define PH_LOG_STATE 2 /* changes in state */ +#define PH_LOG_ACTION 3 /* actions pH takes (delays) */ +#define PH_LOG_IO 4 /* I/O operations (read/write profiles) */ + + +/* note that data contains the bitfields for our sequence info */ +/* */ + +typedef u8 pH_seqflags; + +typedef struct pH_disk_profile_data { + int sequences; /* # sequences that have been inserted */ + /* NOT the number of lookahead pairs */ + unsigned long last_mod_count; /* # syscalls since last modification */ + unsigned long train_count; /* # syscalls seen during training */ + u8 empty[PH_NUM_SYSCALLS]; + pH_seqflags entry[PH_NUM_SYSCALLS][PH_NUM_SYSCALLS]; +} pH_disk_profile_data; + +typedef struct pH_disk_profile { + char magic[PH_FILE_MAGIC_LEN]; /* file magic: identifier, version */ + int normal; + int frozen; + time_t normal_time; + int length; + unsigned long count; + int anomalies; + pH_disk_profile_data train, test; + char filename[PH_MAX_DISK_FILENAME]; +} pH_disk_profile; + + +typedef struct pH_profile_data { + int sequences; /* # sequences that have been inserted */ + /* NOT the number of lookahead pairs */ + unsigned long last_mod_count; /* # syscalls since last modification */ + unsigned long train_count; /* # syscalls seen during training */ + void *pages[PH_MAX_PAGES]; + int current_page; /* pages[current_page] contains free space */ + int count_page; /* how many arrays have been allocated in + the current page */ + pH_seqflags *entry[PH_NUM_SYSCALLS]; +} pH_profile_data; + +typedef struct pH_profile pH_profile; + +struct pH_profile { + int normal; /* is test profile normal? */ + int frozen; /* is train profile frozen (potential normal)? */ + time_t normal_time; /* when will frozen become true normal? */ + int length; + unsigned long count;/* number of calls seen by this profile */ + int anomalies; /* NOT LFC - decide if normal should be reset */ + pH_profile_data train, test; + char *filename; + atomic_t refcount; + pH_profile *next; + struct file *seq_logfile; + struct semaphore lock; +}; + +typedef struct pH_seq { + int last; /* seq is a circular array; */ + /* this is its end */ + int length; + u8 data[PH_MAX_SEQLEN]; /* current sequence being filled */ + /* or processed - initialized to */ + /* PH_EMPTY_SYSCALL initially */ + struct list_head seqList; +} pH_seq; + +typedef struct pH_seq_logrec { + unsigned long count; + int pid; + struct timespec time; + pH_seq seq; +} pH_seq_logrec; + +#define PH_CALLREC_SYSCALL 0 +#define PH_CALLREC_FORK 1 +#define PH_CALLREC_EXECVE 2 + +typedef struct pH_call_logrec { + u16 pid; + union { + u16 syscall; /* type = 0 */ + u16 child_pid; /* type = 1 (fork) */ + u16 filename_len; /* type = 2 (execve) */ + } u; + unsigned long count; + long sec; + long nsec; + u8 type; /* 0 = regular call, 1 = fork, 2, execve */ +} pH_call_logrec; + +typedef struct pH_locality { + u8 win[PH_LOCALITY_WIN]; + int first; + int total; + int max; +} pH_locality; + +typedef struct pH_task_state { + pH_locality alf; + pH_seq *seq; + int delay; + unsigned long count; + pH_profile *profile; /* pointer to appropriate profile */ +} pH_task_state; + +extern void pH_process_syscall(long); +extern void pH_execve(struct file *); +extern void pH_exit(struct task_struct *); +extern void pH_release(struct task_struct *); +extern void pH_fork(struct task_struct *); +extern int do_pH(int op, int arg1); +extern int pH_do_suspend_execve(void); +extern int proc_pid_pH_taskinfo(struct task_struct *, char *); + +#endif /* _LINUX_PH_H */ diff --git a/include/linux/sched.h b/include/linux/sched.h index 1941d8b..f3b55b3 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -88,6 +88,10 @@ struct sched_param { #include #include +#ifdef CONFIG_SECURITY_PH +#include +#endif + #include struct mem_cgroup; @@ -1290,6 +1294,9 @@ struct task_struct { int latency_record_count; struct latency_record latency_record[LT_SAVECOUNT]; #endif +#ifdef CONFIG_SECURITY_PH + pH_task_state pH_state; +#endif }; /* diff --git a/kernel/exit.c b/kernel/exit.c index 93d2711..d584757 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -47,6 +47,10 @@ #include #include +#ifdef CONFIG_SECURITY_PH +#include +#endif + #include #include #include @@ -136,7 +140,6 @@ static void __exit_signal(struct task_struct *tsk) tsk->signal = NULL; tsk->sighand = NULL; spin_unlock(&sighand->siglock); - rcu_read_unlock(); __cleanup_sighand(sighand); clear_tsk_thread_flag(tsk,TIF_SIGPENDING); @@ -169,6 +172,9 @@ void release_task(struct task_struct * p) struct task_struct *leader; int zap_leader; repeat: +#ifdef CONFIG_SECURITY_PH + pH_release(p); +#endif atomic_dec(&p->user->processes); proc_flush_task(p); write_lock_irq(&tasklist_lock); @@ -1074,6 +1080,10 @@ NORET_TYPE void do_exit(long code) tsk->exit_code = code; taskstats_exit(tsk, group_dead); +#ifdef CONFIG_SECURITY_PH + pH_exit(tsk); +#endif + exit_mm(tsk); if (group_dead) diff --git a/kernel/fork.c b/kernel/fork.c index adefc11..4abd9b5 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -56,6 +56,10 @@ #include #include +#ifdef CONFIG_SECURITY_PH +#include +#endif + #include #include #include @@ -1219,6 +1223,10 @@ static struct task_struct *copy_process(unsigned long clone_flags, nr_threads++; } +#ifdef CONFIG_SECURITY_PH + pH_fork(p); +#endif + total_forks++; spin_unlock(¤t->sighand->siglock); write_unlock_irq(&tasklist_lock); diff --git a/pH-ChangeLog b/pH-ChangeLog new file mode 100644 index 0000000..0429b46 --- /dev/null +++ b/pH-ChangeLog @@ -0,0 +1,696 @@ +2008-07-20 Mario Van Velzen + + * Ported pH to 2.6.26. + +2003-06-05 Anil Somayaji + + * pH-0.22 released. + +2003-06-03 Anil Somayaji + + * pH.c: Ported to 2.4.20 + timer patches. We've been using this + on the robot, but now it is all in CVS. Specifically, these + patches were added to a stock 2.4.20: hrtimers-2.4.20-1.0.patch.bz2, + hrtimers-support-2.5.62-1.0.patch.bz2, patch-2.4-defgeode.txt, + variable-hz-rml-2.4.20-pre10-ac2-1.patch.bz2 + (pH_execve): finally fixed the profile path bug. Just had to get + the vfsmount out of exec_file - d'oh! + + +2003-01-31 Anil Somayaji + + * pH.c (pH_execve): Got an oops here, it seems like from the + d_path call, so I added null checks for exec_file and exec_dentry. + Also changed the error handling path, making it free the profile. + If something went wrong, we don't want to keep monitoring the + process with the old executable's profile. + + Added code to cancel responses when alt-sysrq-z is pressed: + (sysrq_handle_pH): added + (sysrq_pH_op): added + (pH_start): register key + (pH_shutdown: unregister key + + (pH_delay_factor): now defaults to 0 + (pH_suspend_execve): now defaults to 0 + + +2003-01-30 Anil Somayaji + + * upgraded to linux-2.4.18-rh19 - Red Hat's version of 2.4.18 + (version 19). Hopefully, this version should be extremely stable. + +2002-10-17 Anil B. Somayaji + + * kernel/pH.c (do_pH): added PH_RESET_LFC case + (pH_start): got rid of successful /proc/pH creation log message + + * include/linux/pH.h (PH_RESET_LFC): added + + * kernel/pH.c (pH_tolerize): broke out function from sys_pH() + (pH_cmd_start_normal): ditto + (pH_cmd_sensitize): ditto + (pH_cmd_reset_profile): ditto + (pH_cmd_reset_lfc): new command + +2002-10-13 Anil B. Somayaji + + The big changes for the 2.4 port (the rest was surprisingly easy + to do, and this wasn't that hard once I got around to it). + + * base.c (proc_base_lookup): Added PROC_PID_PH to case statement + and reference to proc_pid_pH_taskinfo. Note how this entry + follows the pattern of PROC_PID_STAT and such. + + Also added PROC_PID_PH stuff to base_stuff[] structure (the actual + entries in /proc/) and pid_directory_inos (the inode numbers + of these entries). + + * kernel/pH.c: (pH_mkdir) rewrote pH_mkdir() to use + call_usermodehelper() (in kmod.c) to invoke /usr/bin/install in + order to create the directories, instead of doing everything in + kernel space. There may be some races in the initial running of + the system, but nothing that couldn't be solved by + pre-initializing the profiles directory with a few standard paths. + And besides, this code is much cleaner and safer. Note that it + has to remove the trailing part of the path, otherwise the profile + name itself would become a directory. + (proc_pid_pH_taskinfo): was get_pH_taskinfo, now rewritten so it + takes a task_struct instead of a pid. It is now called by + fs/proc/base.c (not array.c). + +2001-09-09 Anil B. Somayaji + + With the following changes, the old abort_execve behavior is gone, + and is replaced with suspend_execve. So, instead of just + returning an error for the execve, we instead just delay it for a + very long time (two days by default). If nobody steps in, then + execution proceeds as before after the delay. If the process is + tolerized or if pH_suspend_execve is set to 0, the process resumes + execution immediately. + + * include/linux/pH.h (pH_do_suspend_execve): fixed declaration + (PH_SUSPEND_EXECVE_TIME): added + + * kernel/pH.c: changed abort_execve to suspend_execve. + (pH_do_suspend_execve): was pH_forbid_execve. Now it waits for + pH_suspend_execve_time if threshold is exceeded. + (do_pH): now can set pH_suspend_execve_time + +2001-09-06 Anil B. Somayaji + + * kernel/pH.c (pH_fork): After parsing the logs, I noticed that + there were many PIDs delayed which I didn't know the corresponding + profile. There is now a message here about inheriting a non-zero + LFC to fix this. + (do_pH): Print the filename of the profile when we tolerize, + sensitize, etc. + +2001-08-17 Anil B. Somayaji + + * kernel/pH.c (pH_log_fork): had another euid test. Ack! It is + gone now, hopefully for good. + (pH_train): added pH_normal_factor_den to freezing calculation. + Now we can have fractional factors, all without floats. + (pH_train): got rid of freezing & thawing log messages + + Also, pH_normal_factor is now unsigned, so all references to it + are now changed accordingly. + +2001-08-13 Anil B. Somayaji + + * kernel/pH.c (get_pH_taskinfo): added a "profile" field which + lists the filename of the corresponding profile. This was needed + so pHmon could easily print out the profile information for a + process. + +2001-08-10 Anil B. Somayaji + + * kernel/pH.c (pH_train): Don't reset train_count and + last_mod_count if thawing a profile. (Note that last_mod_count is + reset anyway.) Otherwise, we have both = 0, making normal_count = + 0 always if no new sequences are changed, so it never becomes + normal. Bad. + +2001-08-09 Anil B. Somayaji + + * kernel/pH.c (pH_process_syscall): Increment pH_syscall_count + always, not just if we are monitoring the current process. + (pH_log_syscall): oops, forgot to get rid of euid test. + +2001-08-08 Anil B. Somayaji + + * kernel/pH.c (pH_train): guess what? We don't need pH_mod_min + anymore either! If normal_count == 0, then it must be the case + that at some point in the past, we copied train to test and + started normal monitoring. If we did this, then there is valid + testing data. normal_count == 0 means basically that we haven't + added anything, so there is no reason ever to copy the training + profile to testing again - it would just be redundant. So, the + condition of (normal_count == 0) && train_count > pH_mod_min is + useless! So, it has been commented out, along with references to + pH_mod_min (except its actual declaration and the sys_pH + reference). + + Funny how things simplify, slowly. + +2001-08-06 Anil B. Somayaji + + * include/linux/pH.h: changed train_count, last_mod_count, and + count fields to all be unsigned long - we don't want any + wraparound! + + * kernel/pH.c: changed pH_syscall_count to an unsigned long, and + added a spinlock. Changed code to use this. Also changed + references to above fields to be unsigned long. + + +2001-07-31 Anil B. Somayaji + + * include/linux/pH.h: Got rid of last_fork field in pH_task_state. + + +2001-07-17 Anil B. Somayaji + + * pH-0.17 released. + + +2001-07-05 Anil B. Somayaji + + * kernel/pH.c (pH_do_delay): if you tolerize, you can get a delay + less than zero. Oops. + +2001-07-01 Anil B. Somayaji + + * kernel/pH.c (pH_delay_task): only generate log message if we + really are delaying the process + +2001-06-28 Anil B. Somayaji + + * kernel/pH.c: Did an extensive rewrite of pH_read_profile, + cleaning up the code. Found a memory leak - if we already had a + profile in memory, it didn't free the page associated with the + filename. Oops! + + Also changed code to use profile->lock as a semaphore, not a + spinlock. (More of the code can now sleep since memory allocation + is spread around.) + +2001-06-25 Anil B. Somayaji + + * kernel/pH.c (pH_process_syscall): Changed this and related + functions to handle profile->frozen field. This field indicates + that it seems like the training profile is normal. If it doesn't + change for normal_wait time (a day, by default), it becomes the + current normal. If a new sequence shows up before then, it is + "un-frozen", and train_count goes to 0. + + * changed pH_debug to pH_loglevel, and reimplemented logging + code. Now everything is done via 4 macros: err, state, action, + io, with loglevels corresponding to each. Each macro checks + pH_loglevel, and if it is >= to the action, does a printk. Most + of pH.c has been changed (and a few changes to pH.h), but the + functionality is basically the same. + +2001-06-24 Anil B. Somayaji + + * kernel/pH.c (pH_log_sequence): added to make pH_train() shorter + Did a bunch to add normal_time functionality: this causes pH to + wait for a certain period of time (default: 1 hour) before + actually using a normal profile. If we get any anomalies in this + time, we throw it away. + (pH_proc_status): added pH_normal_wait + (do_pH): same + (pH_log_status): same + (pH_start_normal): update profile->normal_time + (pH_profile_mem2disk): add normal_time + (pH_profile_disk2mem): same + (pH_process_normal): use normal_time to implement profile->normal + tristate: 0 = not normal, -1 = potential normal, but wait for an + hour to see, 1 = normal, respond to attacks + +2001-06-18 Anil B. Somayaji + + * kernel/fork.c (do_fork): changed call to pH_fork to be before + child process is woken up. + + * include/linux/pH.h: added pH_call_logrec + + * kernel/pH.c (pH_log_execve): added + (pH_log_fork): added + (pH_log_syscall): modified to use new pH_call_logrec struct + + Added appropriate calls to these functions, so now we do more + elaborate system call logging. Also restricted these functions to + only log calls for processes with euid < 1000 (privileged uids, + hopefully). + +2001-06-16 Anil B. Somayaji + + * include/linux/pH.h: Changed pH_profile_data, so pairs are stored + as pointers to arrays, which are only allocated if the + corresponding system call is needed. Added pH_disk_profile_data + (same as old pH_profile_data). + + * kernel/pH.c (pH_profile_disk2mem, pH_profile_mem2disk, + pH_profile_data_mem2disk, pH_profile_disk2mem): added these plus + calls to them to handle reading and writing of new profiles. On + disk, profiles are even bigger (empty array + 256 syscalls + vs. 190) - but at least they are smaller in memory! + +2000-10-30 Anil B. Somayaji + + * kernel/pH.c (pH_slow_fork): Added pH_slow_fork_count, to keep + delaying even after immediate pressure has lifted. Turns out with + old version, a fork bomb doesn't kill the system, but you can't + get rid of it! It's like a hydra. Maybe this will help... + +2000-10-29 Anil B. Somayaji + + * kernel/pH.c (pH_slow_fork): added, called by do_fork(). Instead + of playing with frequencies, how about just slowing things down if + we have too many processes? + +2000-10-26 Anil B. Somayaji + + * kernel/pH.c (pH_freq_update): implemented basic frequency delay + code. + +2000-10-20 Anil B. Somayaji + + * kernel/pH.c (pH_freq): Added a whole bunch of stuff here to + support system call frequency logging. Hopefully this will show + that this method is viable... + +2000-09-22 Anil B. Somayaji + + * kernel/exit.c (release): added call to pH_exit. Should only do + anything if a process dies abnormally. Hopefully this won't cause + any problems... + + * include/linux/pH.h: changed pH_seq_logrec to use struct timeval + + * kernel/pH.c (pH_train): log xtime instead of jiffies + (pH_log_syscall): do the same here + +2000-08-16 Anil B. Somayaji + + * kernel/pH.c (pH_open_seq_logfile): oops, didn't free page in + common case + +2000-08-12 Anil B. Somayaji + + * fs/proc/array.c (process_unauthorized): Needed to add + PROC_PID_PH to case statement - otherwise, only root and the owner + of the process could read /proc//pH! + +2000-08-06 Anil B. Somayaji + + * include/linux/pH.h (PH_MIN_SEQLEN): added for sanity checks. + + * kernel/pH.c (pH_read_profile): use the new pH_disk_profile + (pH_write_profile): same. Note that in both places we vmalloc and + vfree a pH_disk_profile, and do copying to and from it. Will this + slow things down? + (pH_read_profile): test disk_profile->length, make sure it's in + range. (Should we do other sanity checks on the disk profile? + What other ones could we do?) + + * include/linux/pH.h: added PH_FILE_MAGIC string and + PH_FILE_MAGIC_LEN: these will help us only load proper profiles + from disk. + + * kernel/pH.c (pH_free_profile): only write the profile if + pH_aremonitoring is true. We could do this upstream, but we want + to still clean up memory as processes die. We could do this all + at once when monitoring is stopped, but that may be hard. + (pH_read_profile): have it use pH_disk_profile + (pH_write_profile): same + + +2000-08-05 Anil B. Somayaji + + * kernel/pH.c (pH_mkdir): new function, creates needed directories + in path. + (pH_open_logfile): use it + (pH_write_profile): also use it + (pH_open_seq_logfile): there seems to be a possible crash when + /var/lib/pH/profiles isn't properly set up. pH_mkdir seems to + make this mostly go away, but what if this exists but isn't a + directory or something? Probably should investigate at some + point. + (pH_mkdir): Had some major problems with whether to use dput and + dget. The weird thing is that it seems that we shouldn't use dget + or dput at all, _except_ that we need one for current->fs->root. + This makes no sense to me, but some printk's of dentry->d_count + seem to confirm this behavior... + + * include/linux/pH.h: pH_disk_profile and PH_MAX_DISK_FILENAME + added. + + +2000-07-14 Anil B. Somayaji + + Did many random changes to add a pH entry to /proc process + directories: + + * fs/proc/base.c: added struct proc_pid_pH. This is the actual + structure that gives the info for /proc//pH. + (proc_base_init): register it. + + * include/linux/pH.h (get_pH_taskinfo): added declaration + + * kernel/pH.c (get_pH_taskinfo): added function + + * fs/proc/array.c (get_process_array): added call to + get_pH_taskinfo() + + +2000-07-12 Anil B. Somayaji + + * kernel/pH.c (pH_proc_status): was pH_proc_info, now reports the + same info as "pH-admin status"; however, it uses the same rough + format as /proc/cpuinfo. + (pH_start): now pH_proc_status is connected to /proc/pH/status + +2000-07-08 Anil B. Somayaji + + * kernel/pH.c (do_pH): added call to pH_start() + (pH_start): created so we can initialize /proc entries + (pH_proc_info): needed to implement basic /proc file + (pH_shutdown): and we need to get rid of the file on shutdown. + +2000-06-19 Anil B. Somayaji + + * kernel/pH.c (pH_start_monitoring): Don't reset the process + syscall count. + (pH_execve): but now we need to set s->count=0 if we are just + starting monitoring. + (pH_fork): Set s->count to 0 in child - child should have new + count of system calls. + +2000-06-08 Anil B. Somayaji + + * kernel/pH.c (pH_LFC): was pH_anomaly_total + (pH_scaled_anomaly_total): deleted + (pH_execve): don't reset the ALF after an execve + +2000-06-07 Anil B. Somayaji + + * kernel/pH.c (pH_process_normal): made delay code run + unconditionally. Now, LFC determines the delay for all system + calls, whether normal or not, whether the current call is + anomalous or not. + (pH_forbid_execve): added - now, if we have a non-zero LFC, we + don't allow execve's no matter what. + (pH_process_normal): make sure delay is 0 if LFC + (pH_anomaly_total) is zero! + (pH_reset_profile): better name than pH_reset_normal + (pH_stop_normal): new function, now used when we reach the + anomaly_limit and for tolerize system call + (pH_process_normal): now it returns delay_exp (LFC) instead of + delay + (pH_process_syscall): it now compares pH_tolerize_limit with + delay_exp result (LFC). NOTE: Should make delay_exp and LFC usage + uniform - use of two terms in source is confusing. + + * fs/exec.c (do_execve): call pH_forbid_execve, return -EACCESS if + not allowed. (-EACCES is used for so many other things, might as + well do it for pH intervention.) + +2000-04-25 Anil B. Somayaji + + * kernel/pH.c (pH_write_all_profiles): need a \n if you want a + message to be printed to the log! + +2000-04-23 Anil B. Somayaji + + * kernel/pH.c (pH_write_all_profiles): new function + (do_pH): use it. + + * include/linux/pH.h (PH_WRITE_PROFILES): now that we don't + actually write profiles every time a process exits, it would be + good to have a way to explicitly write all in-memory profiles to + disk... + + * kernel/pH.c: These changes should cause pH_write_profile to only + be called just before we actually free a profile. Hopefully this + should eliminate the problems with ftpd on portico reporting that + it couldn't write its profiles. I'm assuming this happened + because too many processes exited at the same time, and so + multiple writes were pending for ftpd. Perhaps this also caused + portico to run out of processes after being up for 2.5 days. One + can hope! + (pH_exit): get rid of call to pH_write_profile + (pH_fork): same + (pH_shutdown): changed call to pH_write_profile to pH_free_profile + OOPS! Can't do that, will leave dangling pointrs in task_structs! + Undone. (Later, should probably go through task_struct list and + clear all of the pointers. But that may be dangerous...) + (pH_execve): eliminated call to pH_write_profile (was just before + pH_free_profile call) + (pH_free_profile): added call to pH_write_profile + + +2000-04-14 Anil B. Somayaji + + * kernel/pH.c (pH_train): add unlock/lock pair around pH_log_bytes + call. It may sleep, so we probably shouldn't be holding a + spinlock. + +2000-04-13 Anil B. Somayaji + + * kernel/pH.c (pH_shutdown): new function, replaces + pH_close_all_seqlogs. This function now also sets + pH_aremonitoring to 0, and saves all of the profiles to disk. + Note that it doesn't actually free the profiles, that might be + dangerous (but eventually should probably be done). Also, now we + set the logfile pointer to NULL before actually trying to close + it; hopefully this will prevent anyone else from trying to write + to the file after it has been closed. + +2000-04-12 Anil B. Somayaji + + * kernel/pH.c (pH_read_profile): Added sequence logfile opening if + pH_log_sequences is true. (Note the code is there twice for the + two branches, creating and reading. Maybe should be abstracted?) + (pH_free_profile): close sequence logfile. (Does so + unconditionally, since pH_close_logfile ignores NULL files.) + (pH_train): actually log the sequence, along with other state + (pH_close_all_seqlogs): added for cleanup + (do_pH): use it. + + * include/linux/pH.h: added pH_seq_logrec to for logging sequences + (PH_LOG_SEQUENCES): also added + + * kernel/pH.c (pH_open_logfile): made generic + (pH_close_logfile): same for close + (pH_open_syscall_logfile): uses generic version to open syscall + log file + (pH_close_syscall_logfile): same for close + (do_pH): added PH_LOG_SEQUENCES action + + +2000-04-10 Anil B. Somayaji + + * kernel/pH.c (pH_add_anomaly_count): now ALF just stores 1 or 0 + for anomalies. Thus the total of the ALF values is the LFC! + Also, we now use a total counter to avoid rescanning the array on + every system call. + (pH_anomaly_total): use alf.total + (pH_process_normal): Calculate delay as 2^(LFC) (using + pH_anomaly_total()), with 2^30 being the maximum delay on 32-bit + machines. As that's over 10 million seconds, I think it should be + enough! + +2000-03-30 Anil B. Somayaji + + * pH.c (pH_add_anomaly): Only increment profile->anomalies if it + really is an anomaly. + (pH_add_anomaly_count): Renamed. + + * kernel/pH.c (do_pH): Added version info to status output + +2000-03-27 Anil B. Somayaji + + * kernel/pH.c: Use logging levels. + (do_pH): Added log messages for changes in pH's state. + + * include/linux/pH.h (PH_DEBUG_INFO): Added debugging message + levels. + + * kernel/pH.c (pH_process_normal): Moved pH_add_anomaly up a line + (to be outside the if (anomalies) test). This should cause the + ALF to be added to on every system call, not on every anomaly. + + * include/linux/pH.h (PH_SYSCALLS): Changed from 200 to 196. + Maybe this is better for address calculations? Whatever the case, + it should save a bit of space. (Note that sys_pH is currently + 195.) + (PH_LOCALITY_WIN): Changed from 20 to 128. Turns out current + codes the last 20 _anomalies_ (not calls) in the ALF array. Based + on the login trojan data, it looks like we need a window >90 to + get a signal bigger than 6. We'll see how this works! + +2000-03-13 Anil B. Somayaji + + * include/linux/pH.h: Modified profile variable order to put + pointers and spinlock at the end. This way the fields we care + about for print_profile are the same for SMP and non-SMP. + + * kernel/pH.c (do_pH): Added CAP_KILL check. Now arbitrary users + can't modify pH's state! + +2000-03-12 Anil B. Somayaji + + * kernel/pH.c (pH_process_syscall): Use the spinlock. Now we lock + for almost the entire duration of the function - maybe this is too + long? I hope we won't need a semaphore. + (pH_process_normal): Lock released during delay, but re-acquired + just afterwards. + (do_pH): Also grab the lock when we modify a profile from pH + system call. + + * include/linux/pH.h: Added "spinlock_t lock" to profile. + + * arch/i386/kernel/entry.S (ret_from_fork): Our save/restore of + %eax messed up the parameter passing (one too many values on the + stack - messes up the pt_regs structure). Now we just grab the + right value from the old values on the stack. + + * include/linux/pH.h: Got rid of pH_syscall_count declaration. + * kernel/pH.c: Made pH_syscall_count atomic_t. Needed to use & in + arg for atomic_t operations because we don't have a pointer to the + struct, but the actual struct. Icky. Now we just need to add + spinlocks to the profiles, and we should be SMP-safe! + +2000-03-11 Anil B. Somayaji + + * arch/i386/kernel/process.c (pH_process_syscall_i386): Added. + + * arch/i386/kernel/entry.S (ret_from_fork): We'll use the fact + that the stack contains all of the registers for the system calls. + Our new function, pH_process_syscall_i386, will send %eax to the + real pH_process_syscall. As long as we don't modify the register + stack values, we should be fine. + + * kernel/pH.c (pH_process_syscall): Changed the current syscall + from a global variable to a function argument. Hopefully this + will be SMP safe. + +2000-03-03 Anil B. Somayaji + + * pH.c (do_pH): changed old PH_TOLERIZE to PH_START_NORMAL. New + PH_TOLERIZE now just sets normal=0. + +2000-03-01 Anil B. Somayaji + + * kernel/pH.c (pH_execve): Now we only reset s->delay and the alf + when we start monitoring anew; otherwise alf and s->delay are + inherited across execve's. + (pH_start_monitoring): removed alf and s->delay resets + +2000-01-27 Anil B. Somayaji + + * kernel/immsec.c (Test): now returns the number of mismatches, + instead of just 1 if there are any mismatches. Hopefully this + will help with detection. + +2000-01-17 Anil B. Somayaji + + * kernel/immsec.c (do_immsec): new function, which will probably + change a lot in the near future + * include/linux/immsec.h (do_immsec): added declaration for + function called by sys_immsec to actually do the work + * arch/i386/kernel/entry.S (error_code): added table entry for + sys_immsec (#195) + * arch/i386/kernel/process.c (sys_immsec): added immsec system + call arch-dependent stub + +1999-11-10 Anil B. Somayaji + + * include/linux/immsec.h: added normal flag and anomalies counter + to immsec_db_t + * kernel/immsec.c (immsec_process_syscall): Now we have the start + of code to delay system calls when we are in a monitoring mode. + (immsec_read_db): initialize normal and anomalies db fields. Note + that normal does not get modified from 0 in kernel - right now + need to do that with an editor. Need to make this at least + partially automatic... + +1999-10-26 Anil B. Somayaji + + * immsec.c (immsec_delay): simple function to delay current task + by "delay" jiffies. (HZ jiffies = 1 second, HZ = 100 on i386) + Right now it sets the task to be in uninterruptible sleep - is + this what we want? + (immsec_execve): added simple test of immsec_delay to delay for 10 + seconds based on immsec_shoulddelay + (immsec_shoulddelay): right now this just looks for a "delay_me" + executable - maybe it should be enhanced? + +1999-09-28 Anil B. Somayaji + + * kernel/immsec.c (immsec_execve): tried out having do_execve pass + the executable dentry. Good news: d_path on it returns the right + pathname! Bad news: something caused it do die horribly once + tracing started - trashed the filesystem. VMWare sure comes in + handy! + (immsec_read_db): Aha! I figured out the problem. I had forgotten + about restrictions on stack usage in the kernel, and was + allocating PAGE_SIZE buffers on the stack! Oops. No wonder + things died horribly. Anyway, now I use __get_free_page and + free_page to allocate these buffers - fast, and just the right + size. Now things seem stable (phew). So, changed things here, + (print_db_list): and here, + (immsec_execve): and here. And indeed, using d_path resolves + symbolic links, and makes relative pathnames absolute. Whee! + Maybe we could still make things hierarchical, but at this point + that is a cosmetic improvement, and not really needed for now. + So, it looks like the next thing to do is to actually do something + with the monitoring databases! Maybe start by keeping track of + number of pairs added each time a process is run? Would probably + need to add a count field to the database, and a count to the + task_struct for the newly added ones. Shouldn't be hard... + +1999-09-27 Anil B. Somayaji + + * kernel/immsec.c (immsec_read_db): Updated to be similar to + immsec_write_db (use higher level filp_open). Also now changes + fsuid/fsgid to 0 (so files are read as root/root). + (immsec_write_db): Files are now also written as root/root. + + Note that with these changes (well, haven't checked with fsgid + change), we can now boot into X, with _all_ processes being + traced! + + Glitches: relative paths and symbolic links for executables aren't + resolved; instead, DBs get unresolved name. Not good. Also, with + every process getting traced, we get _lots_ of databases. Maybe + it should automatically create a hierarchical db tree? (Maybe + need a userspace helper program to create containing directories?) + Also, need to check for permission snafus (can DBs created by one + user be updated by another?). + +1999-09-26 Anil B. Somayaji + + * kernel/immsec.c (immsec_write_db): rewrote based on current + kernel/acct.c. Hopefully this version won't blow up... + Wow! It seems to work! Need to stress test it, but sure seems + promising. Probably has something to do with using filp_open + instead of doing stuff manually, and with actually setting the + inode semaphore. + +1999-09-12 Anil B. Somayaji + + * kernel/immsec.c (immsec_free_db): Wouldn't it be nice to know + what database we are freeing? printf's now all print + db->filename. + + +1999-09-01 Anil B. Somayaji + + * kernel/immsec.c: tested with vmware, immutable disk - seems to + work fine. + (immsec_write_db): commented out skipping code - + maybe it can write databases correctly now, magically? diff --git a/security/Kconfig b/security/Kconfig index 62ed471..98d7bc3 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -115,6 +115,13 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR This value can be changed after boot using the /proc/sys/vm/mmap_min_addr tunable. +config SECURITY_PH + bool "Process Homeostasis Security Hooks" + help + This is the pH module, which examines system calls for + evidence of anomalous behavior. + + If you are unsure how to answer this question, answer N. source security/selinux/Kconfig source security/smack/Kconfig diff --git a/security/Makefile b/security/Makefile index f654260..cfc4285 100644 --- a/security/Makefile +++ b/security/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_SECURITY_PH) += pH.o diff --git a/security/pH.c b/security/pH.c new file mode 100644 index 0000000..2cfbafa --- /dev/null +++ b/security/pH.c @@ -0,0 +1,1975 @@ +/* + * linux/kernel/pH.c + * + * Copyright (C) 1999-2003 Anil Somayaji + * profile access routines Copyright (C) 1999 Julie Rehmeyer + * + * Basic code for pH, a kernel extension for online process intrusion + * detection monitoring and response. + * + * Licensed under the GNU GPL (see COPYING file), and as such has NO + * WARRANTY! In particular, pH modifies the filesystem using + * low-level access routines. Have a good backup, you could lose + * data through disk corruption! + * + * August 16, 2000: pH-0.1, initial release + * July 17, 2001: pH-0.17 + * September 9, 2001: pH-0.18 final version for my dissertation + * + * June 5, 2003: pH-0.22 first public release of version for 2.4 + * + * July 20, 2008: pH-0.30 first public release of version for 2.6 kernels + */ + +static char pH_version[] = "0.30"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define PH_PATH_PREFIX "/var/lib/pH/profiles/" +#define PH_LOG_PATH "/var/lib/pH/all_logfile" + +const char *PH_FILE_MAGIC="pH profile 0.18\n"; + +#ifdef CONFIG_PROC_FS +#define PH_PROC_READ_RETURN(page,start,off,count,eof,len) \ +{ \ + len -= off; \ + if (len < count) { \ + *eof = 1; \ + if (len <= 0) \ + return 0; \ + } else \ + len = count; \ + *start = page + off; \ + return len; \ +} +#else +#define PH_PROC_READ_RETURN(page,start,off,count,eof,len) return 0; +#endif + +#define check_parent(dir, dentry) \ + ((dir) == (dentry)->d_parent && !list_empty(&dentry->d_hash)) + +#define err(format, arg...) \ +{ \ + if (pH_loglevel >= PH_LOG_ERR) { \ + printk(KERN_ERR "pH: " format "\n" , ## arg); \ + } \ +} + +#define state(format, arg...) \ +{ \ + if (pH_loglevel >= PH_LOG_STATE) { \ + printk(KERN_INFO "pH: " format "\n" , ## arg); \ + } \ +} + +#define action(format, arg...) \ +{ \ + if (pH_loglevel >= PH_LOG_ACTION) { \ + printk(KERN_DEBUG "pH: " format "\n" , ## arg); \ + } \ +} + +#define io(format, arg...) \ +{ \ + if (pH_loglevel >= PH_LOG_IO) { \ + printk(KERN_DEBUG "pH: " format "\n" , ## arg); \ + } \ +} + + + +extern unsigned long volatile jiffies; + +/* this was atomic, but now we need a long - so, we could make + a spinlock for this */ +unsigned long pH_syscall_count = 0; +spinlock_t pH_syscall_count_lock = SPIN_LOCK_UNLOCKED; + +pH_profile *pH_profile_list = NULL; +int pH_default_looklen = 9; +struct file *pH_logfile = NULL; +int pH_delay_factor = 0; +unsigned int pH_normal_factor = 128; +#define pH_normal_factor_den 32 /* a define to make the asm better */ +int pH_aremonitoring = 0; +int pH_monitorSignal = 0; +int pH_mod_min = 500; +int pH_normal_min = 5; +int pH_anomaly_limit = 30; /* test reset if profile->anomalies */ + /* exceeds this limit */ +int pH_tolerize_limit = 12; /* train reset if LFC exceeds this limit */ +int pH_loglevel = PH_LOG_ACTION; +int pH_log_sequences = 0; +int pH_suspend_execve = 0; /* min LFC to suspend execve's, 0 = no suspends */ +int pH_suspend_execve_time = 3600 * 24 * 2; /* time to suspend execve's */ +int pH_normal_wait = 7 * 24 * 3600;/* seconds before putting normal to work */ + +DECLARE_MUTEX(pH_profile_list_sem); /* for mod. to list */ + +#ifdef CONFIG_MAGIC_SYSRQ +static void sysrq_handle_pH(int key, struct tty_struct *tty) +{ + int old_delay = pH_delay_factor; + int old_suspend = pH_suspend_execve; + + pH_delay_factor = 0; + pH_suspend_execve = 0; + state("Stopping responses."); + state("delay_factor changed from %d to %d", + old_delay, pH_delay_factor); + state("suspend_execve changed from %d to %d", + old_suspend, pH_suspend_execve); +} + +static struct sysrq_key_op sysrq_pH_op = { + .handler = sysrq_handle_pH, + .help_msg = "Zap-pH", + .action_msg = "Zapping pH's responses", + .enable_mask = SYSRQ_ENABLE_BOOT, +}; +#endif /* CONFIG_MAGIC_SYSRQ */ + +void pH_do_delay(unsigned long delay) +{ + /* maybe we shouldn`t allow interrupts here? */ + + current->pH_state.delay = delay; + + while ((current->pH_state.delay > 0) && + (pH_delay_factor > 0)) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(pH_delay_factor); + (current->pH_state.delay)--; + } + + if (current->pH_state.delay < 0) + current->pH_state.delay = 0; +} + +/* + pH_mkdir: creates any directories that don't exist in path leading + up to filename. Returns 1 on success, 0 otherwise. +*/ + +char install_path[64] = "/usr/bin/install"; + +int pH_mkdir(char *filename) +{ + char *path = (char *) __get_free_page(GFP_USER); + int len, path_len; + int result = 0; + static char * envp[] = { "HOME=/", "TERM=linux", + "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }; + char *argv[] = { install_path, "-d", path, NULL }; + + if (!path) + return 0; + + if (!filename || + ((len = strlen(filename)) > (PAGE_SIZE - 1)) || + /* we want a full path - reject anything else */ + (filename[0] != '/')) { + goto done; + } + + strncpy(path, filename, len); + + path_len = len - 1; + while (path[path_len] != '/') { + path_len--; + } + if (path_len < 2) + goto done; + path[path_len] = '\0'; + path_len--; + + result = call_usermodehelper(install_path, argv, envp, 0); + + done: + free_page((unsigned long) path); + return result; +} + +struct file *pH_open_logfile(char *filename) +{ + struct file *file; + int old_fsuid = current->fsuid; + int old_fsgid = current->fsgid; + + current->fsuid = 0; + current->fsgid = 0; + file = filp_open(filename, + O_WRONLY|O_APPEND|O_CREAT, 0644); + if (IS_ERR(file)) { + pH_mkdir(filename); + file = filp_open(filename, O_WRONLY|O_APPEND|O_CREAT, 0644); + } + + current->fsuid = old_fsuid; + current->fsgid = old_fsgid; + + if (IS_ERR(file)) + return NULL; + + if ((!S_ISREG(file->f_dentry->d_inode->i_mode)) || + (!file->f_op->write)) { + filp_close(file, NULL); + return NULL; + } else { + return file; + } +} + +int pH_close_logfile(struct file *file) +{ + if (file) { + return(filp_close(file, NULL)); + } else { + return 0; /* ignore closes of non-existent files*/ + } +} + +void pH_close_syscall_logfile(void) +{ + int result; + + if (pH_logfile) { + result = pH_close_logfile(pH_logfile); + pH_logfile = NULL; + if (result) { + err("Problem closing " PH_LOG_PATH + ", returned %d.", result); + } else { + state("Closing " PH_LOG_PATH); + } + } else { + err("No logfile to close!"); + } +} + +void pH_open_syscall_logfile(void) +{ + struct file *file; + + /* allows a logfile to be renamed then opened, as with syslog */ + if (pH_logfile) { + pH_close_syscall_logfile(); + } + + file = pH_open_logfile(PH_LOG_PATH); + if (file == NULL) { + err("Couldn't open " PH_LOG_PATH); + return; /* logfile is still NULL */ + } else { + state("Opening " PH_LOG_PATH); + pH_logfile = file; + } +} + +void pH_log_bytes(struct file *file, char *buf, int count) +{ + mm_segment_t fs; + struct inode *inode; + + if (!file) + return; + + atomic_inc(&(file->f_count)); + + fs = get_fs(); + set_fs(get_ds()); + + inode = file->f_dentry->d_inode; + down_write(&inode->i_alloc_sem); + file->f_op->write(file, buf, + count, &file->f_pos); + up_write(&inode->i_alloc_sem); + + set_fs(fs); + fput(file); +} + +inline void pH_log_syscall(long syscall) +{ + if (pH_logfile) { + pH_call_logrec logrec; + + logrec.type = PH_CALLREC_SYSCALL; + logrec.pid = current->pid; + logrec.u.syscall = syscall; + logrec.count = pH_syscall_count; + + /* technically, we should grab the lock here, but hey, we're just + logging, and it probably isn't worth the cycles */ + logrec.sec = xtime.tv_sec; + logrec.nsec = xtime.tv_nsec; + + pH_log_bytes(pH_logfile, (char *) &logrec, + sizeof(pH_call_logrec)); + } +} + +inline void pH_log_fork(int child_pid) +{ + if (pH_logfile) { + pH_call_logrec logrec; + + logrec.type = PH_CALLREC_FORK; + logrec.pid = current->pid; + logrec.u.child_pid = child_pid; + logrec.count = pH_syscall_count; + + logrec.sec = xtime.tv_sec; + logrec.nsec = xtime.tv_nsec; + + pH_log_bytes(pH_logfile, (char *) &logrec, + sizeof(pH_call_logrec)); + } +} + + + +inline void pH_log_execve(char *filename) +{ + if (pH_logfile) { + int fn_len; + pH_call_logrec logrec; + + /* include the null byte */ + fn_len = strlen(filename) + 1; + + logrec.type = PH_CALLREC_EXECVE; + logrec.pid = current->pid; + logrec.u.filename_len = fn_len; + logrec.count = pH_syscall_count; + + logrec.sec = xtime.tv_sec; + logrec.nsec = xtime.tv_nsec; + + pH_log_bytes(pH_logfile, (char *) &logrec, + sizeof(pH_call_logrec)); + + pH_log_bytes(pH_logfile, filename, fn_len); + } +} + +inline int pH_monitoring(struct task_struct *tsk) +{ + return (tsk->pH_state.profile != NULL); +} + +inline int pH_profile_in_use(pH_profile *profile) +{ + return (atomic_read(&(profile->refcount)) > 0); +} + +inline void pH_refcount_inc(pH_profile *profile) +{ + atomic_inc(&(profile->refcount)); +} + +inline int pH_refcount_dec_and_test(pH_profile *profile) +{ + return atomic_dec_and_test(&(profile->refcount)); +} + +inline void pH_refcount_dec(pH_profile *profile) +{ + atomic_dec(&(profile->refcount)); +} + +inline void pH_refcount_init(pH_profile *profile, int i) +{ + profile->refcount.counter = i; +} + +char *pH_profile_filename(const char *source, char *dest, int maxlen) +{ + int i; + char *result; + + result = dest; + + /* hey, it's a constant, don't bother checking length */ + i = strlen(PH_PATH_PREFIX); + strcpy(dest, PH_PATH_PREFIX); + + dest += strlen(PH_PATH_PREFIX); + source++; + + while ((*source != '\0') && (i < (maxlen - 1))) { + /* + if (*source == '/') + *dest = '_'; + else + *dest = *source; */ + *dest = *source; + + source++; + dest++; + i++; + } + *dest = '\0'; + + return result; +} + + +/* note: does not hold any sort of lock! */ +pH_profile *pH_find_profile(const char *filename, pH_profile *first_profile) +{ + pH_profile *profile = first_profile; + + while (profile != NULL) { + if (strcmp(profile->filename, filename) == 0) + break; + profile = profile->next; + } + return profile; +} + +void pH_open_seq_logfile(pH_profile *profile) +{ + char *seq_filename = (char *) __get_free_page(GFP_USER); + int len; + + if (!seq_filename) + return; + if (pH_log_sequences && + seq_filename && + profile->filename && + ((len = strlen(profile->filename)) < PAGE_SIZE - 5)) { + strcpy(seq_filename, profile->filename); + strcpy(seq_filename + len, ".seq"); + profile->seq_logfile = pH_open_logfile(seq_filename); + } else { + profile->seq_logfile = NULL; + } + free_page((unsigned long) seq_filename); +} + +void pH_add_new_profile(pH_profile *profile, char *filename) +{ + int i; + + io("Creating profile %s", filename); + + profile->normal = 0; /* we just started - not normal yet! */ + profile->frozen = 0; + profile->normal_time = 0; + profile->anomalies = 0; + profile->length = pH_default_looklen; + profile->count = 0; + init_MUTEX(&(profile->lock)); + + profile->train.sequences = 0; + profile->train.last_mod_count = 0; + profile->train.train_count = 0; + profile->train.current_page = 0; + profile->train.count_page = 0; + for (i=0; itrain.entry[i] = NULL; + } + + for (i=0; itrain.pages[i] = NULL; + } + + profile->test = profile->train; + + profile->next = NULL; + pH_refcount_init(profile, 0); + profile->filename = filename; + + pH_open_seq_logfile(profile); + + profile->next = pH_profile_list; + pH_profile_list = profile; +} + +int pH_add_seq_storage(pH_profile_data *data, int val) +{ + pH_seqflags *page; + + if (data->count_page >= PH_COUNT_PAGE_MAX) { + data->current_page++; + data->count_page = 0; + } + + if (data->current_page >= PH_MAX_PAGES) + return -1; + + if (data->count_page == 0) { + page = (pH_seqflags *) __get_free_page(GFP_USER); + if (page) + data->pages[data->current_page] = page; + else + return -1; + } else { + page = data->pages[data->current_page]; + } + + data->entry[val] = page + (data->count_page * PH_NUM_SYSCALLS); + data->count_page++; + + return 0; +} + +void pH_profile_data_mem2disk(pH_profile_data *mem, pH_disk_profile_data *disk) +{ + int i, j; + + disk->sequences = mem->sequences; + disk->last_mod_count = mem->last_mod_count; + disk->train_count = mem->train_count; + + for (i = 0; i < PH_NUM_SYSCALLS; i++) { + if (mem->entry[i] == NULL) { + disk->empty[i] = 1; + for (j = 0; j < PH_NUM_SYSCALLS; j++) { + disk->entry[i][j] = 0; + } + } else { + disk->empty[i] = 0; + memcpy(disk->entry[i], mem->entry[i], PH_NUM_SYSCALLS); + } + } +} + +void pH_profile_mem2disk(pH_profile *profile, pH_disk_profile *disk_profile) +{ + /* make sure magic is less than PH_FILE_MAGIC_LEN! */ + strcpy(disk_profile->magic, PH_FILE_MAGIC); + disk_profile->normal = profile->normal; + disk_profile->frozen = profile->frozen; + disk_profile->normal_time = profile->normal_time; + disk_profile->length = profile->length; + disk_profile->count = profile->count; + disk_profile->anomalies = profile->anomalies; + strncpy(disk_profile->filename, profile->filename, + PH_MAX_DISK_FILENAME); + + pH_profile_data_mem2disk(&(profile->train), &(disk_profile->train)); + pH_profile_data_mem2disk(&(profile->test), &(disk_profile->test)); +} + +int pH_profile_data_disk2mem(pH_disk_profile_data *disk, pH_profile_data *mem) +{ + int i; + + mem->sequences = disk->sequences; + mem->last_mod_count = disk->last_mod_count; + mem->train_count = disk->train_count; + + for (i = 0; i < PH_NUM_SYSCALLS; i++) { + if (disk->empty[i]) { + mem->entry[i] = NULL; + } else { + if (pH_add_seq_storage(mem, i)) + return -1; + memcpy(mem->entry[i], disk->entry[i], PH_NUM_SYSCALLS); + } + } + + return 0; +} + +int pH_profile_disk2mem(pH_disk_profile *disk_profile, pH_profile *profile) +{ + profile->normal = disk_profile->normal; + profile->frozen = disk_profile->frozen; + profile->normal_time = disk_profile->normal_time; + profile->length = disk_profile->length; + profile->count = disk_profile->count; + profile->anomalies = disk_profile->anomalies; + + if (pH_profile_data_disk2mem(&(disk_profile->train), + &(profile->train))) + return -1; + + if (pH_profile_data_disk2mem(&(disk_profile->test), + &(profile->test))) + return -1; + + return 0; +} + + +/* frees all the pages used by the profile (except the one + pointed to by profile itself) */ +void pH_free_profile_storage(pH_profile *profile) +{ + int i; + unsigned long x; + + free_page((unsigned long) profile->filename); + profile->filename = NULL; + + for (i = 0; i < PH_MAX_PAGES; i++) { + x = (unsigned long) profile->train.pages[i]; + if (x) { + free_page(x); + } + x = (unsigned long) profile->test.pages[i]; + if (x) + free_page(x); + } +} + +pH_profile *pH_read_profile(char *exec_file) +{ + int result; + struct file *profile_file = NULL; + pH_profile *profile = NULL; + pH_disk_profile *disk_profile = NULL; + char *filename = (char *) __get_free_page(GFP_USER); + mm_segment_t old_fs = get_fs(); + int old_fsuid = current->fsuid; + int old_fsgid = current->fsgid; + + if (!filename) { + err("Out of memory, no free pages!"); + return NULL; + } + + pH_profile_filename(exec_file, filename, PH_MAX_FILENAME); + + down(&pH_profile_list_sem); + profile = pH_find_profile(filename, pH_profile_list); + if (profile) { + up(&pH_profile_list_sem); + free_page((unsigned long) filename); + return profile; + } + + profile = (pH_profile *) __get_free_page(GFP_USER); + if (!profile) { + up(&pH_profile_list_sem); + err("Out of memory - profile!"); + free_page((unsigned long) filename); + return NULL; + } + + pH_add_new_profile(profile, filename); + down(&profile->lock); + up(&pH_profile_list_sem); + + /* profile added to list, now just need to fill in */ + + set_fs(get_ds()); + current->fsuid = 0; + current->fsgid = 0; + + profile_file = filp_open(filename, O_RDONLY, 0); + if (IS_ERR(profile_file)) { + goto exit_nofile; + } + + if (!S_ISREG(profile_file->f_dentry->d_inode->i_mode)) { + err("(pH_read_profile) %s not a regular file", filename); + goto exit_badfile; + } + + if (!profile_file->f_op->read) { + err("%s has no read operation", filename); + goto exit_badfile; + } + + disk_profile = (pH_disk_profile *) vmalloc(sizeof(pH_disk_profile)); + if (!disk_profile) { + err("Out of memory - disk_profile!"); + goto exit_nomem; + } + + result = profile_file->f_op->read(profile_file, + (void *) disk_profile, + sizeof(pH_disk_profile), + &(profile_file->f_pos)); + + if ((result != sizeof(pH_disk_profile)) || + strncmp(disk_profile->magic, PH_FILE_MAGIC, + strlen(PH_FILE_MAGIC)) || + (disk_profile->length > PH_MAX_SEQLEN) || + (disk_profile->length < PH_MIN_SEQLEN)) { + err("Bad disk profile %s!", filename); + goto exit_badread; + } + + io("Read %s in %d with %d train and %d test sequences", + filename, current->pid, disk_profile->train.sequences, + disk_profile->test.sequences); + /* copy the disk parts to the in-core version */ + + if (pH_profile_disk2mem(disk_profile, profile)) { + err("Couldn't allocate mem for profile data for %s", + filename); + /* note: this also frees the filename page */ + pH_free_profile_storage(profile); + up(&profile->lock); + free_page((unsigned long) profile); + profile = NULL; + } + + exit_badread: + vfree((void *) disk_profile); + exit_nomem: + exit_badfile: + filp_close(profile_file, NULL); + exit_nofile: + set_fs(old_fs); + current->fsuid = old_fsuid; + current->fsgid = old_fsgid; + if (profile) + up(&profile->lock); + return profile; +} + + +/* originally copied from kernel/acct.c */ +int pH_write_profile(pH_profile *profile) +{ + /* open file */ + struct inode *inode; + int result; + struct file *profile_file = NULL; + mm_segment_t old_fs; + pH_disk_profile *disk_profile = NULL; + int old_fsuid = current->fsuid; + int old_fsgid = current->fsgid; + char *filename; + + if (profile == NULL) { + err("no profile to write"); + return 0; + } + + filename = (char *) __get_free_page(GFP_USER); + if (!filename) { + err("Out of memory, pH_write_profile (filename)"); + } + + disk_profile = (pH_disk_profile *) vmalloc(sizeof(pH_disk_profile)); + if (!disk_profile) { + free_page((unsigned long) filename); + err("Out of memory, pH_write_profile (disk_profile)"); + return -1; + } + + down(&profile->lock); + strncpy(filename, profile->filename, PH_MAX_FILENAME); + pH_profile_mem2disk(profile, disk_profile); + up(&profile->lock); + + current->fsuid = 0; + current->fsgid = 0; + + profile_file = filp_open(filename, O_CREAT | O_TRUNC, 0644); + if (IS_ERR(profile_file)) { + pH_mkdir(filename); + profile_file = filp_open(filename, O_CREAT | O_TRUNC, 0644); + if (IS_ERR(profile_file)) { + err("Couldn't open %s for writing", filename); + result = -1; + goto out; + } + } + + if (!S_ISREG(profile_file->f_dentry->d_inode->i_mode)) { + err("(pH_write_profile) %s not a regular file", + filename); + result = -2; + goto out_err; + } + + if (!profile_file->f_op->write) { + err("(pH_write_profile) %s has no write operation", + profile->filename); + result = -3; + goto out_err; + } + + old_fs = get_fs(); + set_fs(get_ds()); + + inode = profile_file->f_dentry->d_inode; + + result = profile_file->f_op->write(profile_file, + (void *) disk_profile, + sizeof(pH_disk_profile), + &(profile_file->f_pos)); + + /* Do we need to notify folks that we wrote a profile? */ + if (result > 0) + dnotify_parent(profile_file->f_dentry, DN_MODIFY); + + set_fs(old_fs); + + filp_close(profile_file, NULL); + + if (result == sizeof(pH_disk_profile)) { + io("Wrote %s in %d with %d train and %d test sequences", + filename, current->pid, + disk_profile->train.sequences, + disk_profile->test.sequences); + } else { + err("Tried writing %s, but got %d result.", filename, result); + } + + out: + vfree((void *) disk_profile); + free_page((unsigned long) filename); + current->fsuid = old_fsuid; + current->fsgid = old_fsgid; + return result; + + out_err: + filp_close(profile_file, NULL); + goto out; +} + +/* should be be tracing this file? */ +inline int pH_shouldtrace(char *filename) +{ + return (pH_aremonitoring); + + /* + return ((strcmp(filename, "/usr/bin/emacs-20.3") == 0) || + (strcmp(filename, "/bin/login") == 0)); + */ +} + +void print_profile_list(char *msg) +{ + pH_profile *cur_profile; + char *buf = (char *) __get_free_page(GFP_USER); + char *c; + int n, flen; + + c = buf; + c[0] = '\0'; + n = 0; + flen = 0; + cur_profile = pH_profile_list; + + while (cur_profile != NULL) { + flen = strlen(cur_profile->filename) + 1; + if (n + flen < PAGE_SIZE) { + sprintf(c, " %s", cur_profile->filename); + n += flen; + c += flen; + cur_profile = cur_profile->next; + } else + break; + } + + printk(KERN_INFO "%s:%s\n", msg, buf); + + free_page((unsigned long) buf); +} + + +/* helper for pH_free_profile - needs its context! */ +void pH_remove_profile_from_list(pH_profile *profile) +{ + pH_profile *prev_profile, *cur_profile; + + down(&pH_profile_list_sem); + + if (pH_profile_list == profile) { + pH_profile_list = profile->next; + } else if (pH_profile_list == NULL) { + err("pH_profile_list is NULL when trying to free profile %s", + profile->filename); + } else { + prev_profile = pH_profile_list; + cur_profile = prev_profile->next; + while ((cur_profile != profile) && (cur_profile != NULL)) { + prev_profile = cur_profile; + cur_profile = prev_profile->next; + } + if (cur_profile == profile) { + prev_profile->next = cur_profile->next; + } else { + err("while freeing, couldn't find profile %s in " + "pH_profile_list", profile->filename); + } + } + up(&pH_profile_list_sem); +} + + +void pH_free_profile(pH_profile *profile) +{ + if (profile == NULL) { + err("no profile to free!"); + return; + } + + pH_refcount_dec(profile); + + if (!pH_profile_in_use(profile)) { + pH_remove_profile_from_list(profile); + + pH_close_logfile(profile->seq_logfile); + profile->seq_logfile = NULL; + + if (pH_aremonitoring) + pH_write_profile(profile); + + pH_free_profile_storage(profile); + free_page((unsigned long) profile); + } +} + +inline void pH_reset_ALF(pH_task_state *s) +{ + int i; + + for (i=0; ialf.win[i]=0; + } + s->alf.total = 0; + s->alf.max = 0; + s->alf.first = PH_LOCALITY_WIN - 1; + + /* if there are no anomalies, we don't delay, so zero it now */ + s->delay = 0; +} + +inline int pH_LFC(pH_task_state *s) +{ + return (s->alf.total); +} + +inline int pH_maxLFC(pH_task_state *s) +{ + return (s->alf.max); +} + +inline void pH_add_anomaly_count(pH_task_state *s, int val) +{ + int i = (s->alf.first + 1) % PH_LOCALITY_WIN; + + if (val > 0) { + s->profile->anomalies++; + if (s->alf.win[i] == 0) { + s->alf.win[i] = 1; + s->alf.total++; + if (s->alf.total > s->alf.max) + s->alf.max = s->alf.total; + } + } else if (s->alf.win[i] > 0) { + s->alf.win[i] = 0; + s->alf.total--; + } + s->alf.first = i; +} + +void pH_start_monitoring(pH_task_state *s, pH_profile *profile) +{ + int i; + + if (profile != NULL) { + if (s->seq == NULL) { + pH_seq *temp = (pH_seq *) vmalloc(sizeof(pH_seq)); + s->seq = temp; + INIT_LIST_HEAD(&temp->seqList); + } + s->seq->length = profile->length; + s->seq->last = profile->length - 1; + + /* FIXME: move length to profiles!*/ + + for (i=0; iseq->data[i] = PH_EMPTY_SYSCALL; + } + + pH_refcount_inc(profile); + + s->profile = profile; + } else { + err("trying to start, but no PROFILE!"); + } +} + +inline void pH_end_monitoring(pH_task_state *s) +{ + pH_free_profile(s->profile); + s->profile = NULL; +} + +inline void pH_clear_state(pH_task_state *s) +{ + s->profile = NULL; +} + +inline void pH_copy_state(pH_task_state *old_s, pH_task_state *new_s) +{ + *new_s = *old_s; + pH_refcount_inc(new_s->profile); +} + +/* are we allowed to do an execve? */ +int pH_do_suspend_execve(void) +{ + int maxLFC; + + maxLFC = pH_maxLFC(&(current->pH_state)); + + if ((pH_suspend_execve > 0) && (maxLFC >= pH_suspend_execve)) { + action("Suspending %d execve at %lu for %d seconds.", + current->pid, current->pH_state.count, + pH_suspend_execve_time); + + current->pH_state.delay = pH_suspend_execve_time * HZ; + + while ((current->pH_state.delay > 0) && + (pH_suspend_execve > 0)) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(HZ); + current->pH_state.delay -= HZ; + } + + if (current->pH_state.delay < 0) + current->pH_state.delay = 0; + } + return 0; +} + +void pH_execve(struct file *exec_file) +{ + pH_profile *profile; + pH_task_state *s; + struct path path; + char *pathbuf = (char *) __get_free_page(GFP_USER); + char *filename; + struct dentry *exec_dentry; + + if (!pH_aremonitoring) { + goto exit; + } + + s = &(current->pH_state); + + if (!pathbuf) { + err("Out of memory, pH_execve!"); + goto err_exit; + } + + if (!exec_file) { + err("Null exec_file, pH_execve"); + goto err_exit; + } + + exec_dentry = exec_file->f_dentry; + + if (!exec_dentry) { + err("Null exec_dentry, pH_execve"); + goto err_exit; + } + + path.dentry = exec_dentry; + path.mnt = exec_file->f_vfsmnt; + filename = d_path(&path, pathbuf, PAGE_SIZE - 1); + pH_log_execve(filename); + + if (!pH_monitoring(current)) { + if (pH_shouldtrace(filename)) { + profile = pH_read_profile(filename); + if (!profile) + goto exit; + pH_start_monitoring(s,profile); + s->count = 0; + pH_reset_ALF(s); + } + } else { + profile = s->profile; + pH_free_profile(profile); + profile = pH_read_profile(filename); + if (profile != NULL) { + pH_start_monitoring(s, profile); + } else { + pH_clear_state(s); + } + } + + exit: + if (pathbuf) + free_page((unsigned long) pathbuf); + return; + + err_exit: + pH_free_profile(s->profile); + pH_clear_state(s); + goto exit; +} + +void pH_fork(struct task_struct *p) +{ + pH_task_state *s; + + pH_log_fork(p->pid); + + if (pH_monitoring(current) && pH_aremonitoring) { + s = &(p->pH_state); + pH_copy_state(&(current->pH_state),s); + s->count = 0; + if (current->pH_state.alf.total > 0) + action("On fork, PID %d inheriting LFC %d " + "from PID %d (%s)", p->pid, + current->pH_state.alf.total, current->pid, + current->pH_state.profile->filename); + } else { + pH_clear_state(&(p->pH_state)); + } + + return; +} + +void pH_exit(struct task_struct *tsk) +{ + pH_task_state *s; + + if (pH_monitoring(tsk)) { + s = &(tsk->pH_state); + pH_end_monitoring(s); + } + return; +} + +/* if this gets called, just decrease the usage count of the profile, + don't actually try to free or save it - that could cause a sleep, + and a race. */ +void pH_release(struct task_struct *tsk) +{ + pH_task_state *s; + + if (pH_monitoring(tsk)) { + s = &(tsk->pH_state); + err("Releasing, but not freeing %s\n in PID %d", + s->profile->filename, tsk->pid); + pH_refcount_dec(s->profile); + s->profile = NULL; + } + return; +} + +void pH_add_seq(pH_seq *s, pH_profile_data *data) +{ + int i, cur_call, prev_call, cur_idx; + u8 *seqdata = s->data; + int seqlen = s->length; + + cur_idx = s->last; + cur_call = seqdata[cur_idx]; + for (i = 1; i < seqlen; i++) { + if (data->entry[cur_call] == NULL) { + if (pH_add_seq_storage(data, cur_call)) + return; + } + prev_call = seqdata[(cur_idx + seqlen - i) % seqlen]; + data->entry[cur_call][prev_call] |= (1 << (i - 1)); + } +} + +int pH_test_seq(pH_seq *s, pH_profile_data *data) +{ + int i, cur_call, prev_call, cur_idx; + u8 *seqdata = s->data; + int seqlen = s->length; + int mismatches = 0; + + cur_idx = s->last; + cur_call = seqdata[cur_idx]; + + if (data->entry[cur_call] == NULL) + return (seqlen - 1); + + for (i = 1; i < seqlen; i++) { + prev_call = seqdata[(cur_idx + seqlen - i) % seqlen]; + if ((data->entry[cur_call][prev_call] & + (1 << (i - 1))) == 0) { + mismatches++; + } + } + + return mismatches; +} + + +inline void pH_append_call(pH_seq *s, int new_value) +{ + s->last = (s->last + 1) % (s->length); + s->data[s->last] = new_value; +} + +int pH_copy_train_to_test(pH_profile *profile) +{ + pH_profile_data *train = &(profile->train); + pH_profile_data *test = &(profile->test); + int i; + + test->sequences = train->sequences; + test->last_mod_count = train->last_mod_count; + test->train_count = train->train_count; + + test->current_page = 0; + test->count_page = 0; + + for (i = 0; i < PH_NUM_SYSCALLS; i++) { + if (train->entry[i] == NULL) + test->entry[i] = NULL; + else { + if (pH_add_seq_storage(test, i)) + return -1; + memcpy(test->entry[i], train->entry[i], + PH_NUM_SYSCALLS); + } + } + + return 0; +} + +void pH_start_normal(pH_task_state *s) +{ + pH_profile *profile = s->profile; + pH_profile_data *train = &(profile->train); + pH_profile_data *test = &(profile->test); + + pH_reset_ALF(s); + + if (pH_copy_train_to_test(profile)) + return; + + profile->anomalies = 0; + profile->normal = 1; + profile->frozen = 0; + train->last_mod_count = 0; + train->train_count = 0; + state("%d now has %lu training calls and %lu since last change", + current->pid, test->train_count, test->last_mod_count); + state("Starting normal monitoring in %d (%s) at %lu (%lu, %lu) " + "with %d sequences", current->pid, profile->filename, + profile->count, s->count, pH_syscall_count, + test->sequences); +} + +void pH_stop_normal(pH_task_state *s) +{ + s->profile->normal = 0; + pH_reset_ALF(s); +} + +void pH_free_profile_data(pH_profile_data *data) +{ + int i; + + data->current_page = 0; + data->count_page = 0; + + for (i=0; ientry[i] = NULL; + + for (i = 0; i pages[i]) { + free_page((unsigned long) data->pages[i]); + data->pages[i] = NULL; + } + } +} + +void pH_reset_profile_data(pH_profile_data *data) +{ + data->last_mod_count = 0; + data->train_count = 0; + data->sequences = 0; + + pH_free_profile_data(data); +} + +inline void pH_reset_train(pH_task_state *s) +{ + pH_profile *profile = s->profile; + pH_profile_data *train = &(profile->train); + + pH_reset_profile_data(train); +} + +void pH_reset_profile(pH_task_state *s) +{ + pH_profile *profile = s->profile; + pH_profile_data *train = &(profile->train); + pH_profile_data *test = &(profile->test); + + pH_reset_ALF(s); + profile->anomalies = 0; + profile->normal = 0; + profile->frozen = 0; + profile->normal_time = 0; + profile->count = 0; + + pH_reset_profile_data(train); + pH_reset_profile_data(test); +} + +inline void pH_delay_task(pH_task_state *s, int delay_exp) +{ + if ((pH_delay_factor > 0) && (delay_exp > 0)) { + unsigned long delay, eff_delay; + const int max_delay_exp = sizeof(delay) * 8 - 2; + + if (delay_exp > max_delay_exp) + delay_exp = max_delay_exp; + delay = 1 << delay_exp; + eff_delay = delay * pH_delay_factor; + action("Delaying %d at %lu for %lu jiffies", + current->pid, s->count, eff_delay); + pH_do_delay(delay); + } +} + + +inline void pH_process_normal(pH_task_state *s, long syscall) +{ + int anomalies; + pH_seq *seq = s->seq; + pH_profile *profile = s->profile; + pH_profile_data *test = &(profile->test); + + if (profile->normal) { + anomalies = pH_test_seq(seq, test); + if (anomalies) { + action("Anomalous %ld (%d misses), PID %d (%s), " + "count %lu", syscall, anomalies, current->pid, + profile->filename, s->count); + if (profile->anomalies > pH_anomaly_limit) { + pH_stop_normal(s); + state("Anomaly limit %d exceeded for %d (%s) " + "at %lu, normal reset", + pH_anomaly_limit, current->pid, + profile->filename, s->count); + } + } + } else { + anomalies = 0; + } + + pH_add_anomaly_count(s,anomalies); +} + +inline void pH_log_sequence(pH_profile *profile, pH_seq *seq) +{ + if (profile->seq_logfile && pH_log_sequences) { + pH_seq_logrec rec; + + rec.count = profile->count; + rec.pid = current->pid; + /* should probably grab the xtime lock, but for now + it isn't worth the cycles */ + rec.time = xtime; + rec.seq = *seq; + + up(&profile->lock); + pH_log_bytes(profile->seq_logfile, + (char *) &rec, + sizeof(rec)); + /* lock isn't really needed after this, but + since the rest of the code assumes we have it, + we grab it again */ + down(&profile->lock); + } +} + +inline void pH_train(pH_task_state *s) +{ + pH_seq *seq = s->seq; + pH_profile *profile = s->profile; + pH_profile_data *train = &(profile->train); + + train->train_count++; + if (pH_test_seq(seq, train)) { + if (profile->frozen) { + profile->frozen = 0; + //action("%d (%s) normal cancelled", + // current->pid, profile->filename); + } + pH_add_seq(seq,train); + train->sequences++; + train->last_mod_count = 0; + + pH_log_sequence(profile, seq); + } else { + unsigned long normal_count; + + train->last_mod_count++; + + if (profile->frozen) + return; + + normal_count = train->train_count - + train->last_mod_count; + + if ((normal_count > 0) && + ((train->train_count * pH_normal_factor_den) > + (normal_count * pH_normal_factor))) { + //action("%d (%s) frozen", + // current->pid, profile->filename); + profile->frozen = 1; + profile->normal_time = xtime.tv_sec + pH_normal_wait; + } + } +} + +void pH_process_syscall(long syscall) +{ + spin_lock(&pH_syscall_count_lock); + pH_syscall_count++; + spin_unlock(&pH_syscall_count_lock); + + pH_log_syscall(syscall); + + if (pH_monitoring(current) && pH_aremonitoring) { + int LFC; + pH_task_state *s = &(current->pH_state); + pH_seq *seq = s->seq; + pH_profile *profile = s->profile; + + s->count++; + pH_append_call(seq, syscall); + + down(&profile->lock); + + profile->count++; + pH_train(s); + if (profile->frozen && (xtime.tv_sec > profile->normal_time)) + pH_start_normal(s); + + pH_process_normal(s, syscall); + + LFC = pH_LFC(s); + if (LFC > pH_tolerize_limit) { + action("tolerization limit %d exceeded " + "by PID %d (LFC=%d), train for %s reset", + pH_tolerize_limit, current->pid, + LFC, profile->filename); + pH_reset_train(s); + /* to stop anom_limit from kicking in... */ + profile->anomalies = 0; + } + + up(&profile->lock); + + pH_delay_task(s, LFC); + } +} + +/* shut down pH monitoring completely */ +void pH_shutdown(void) +{ + pH_profile *profile; + struct file *logfile; + + if (pH_aremonitoring) { + pH_aremonitoring = 0; + +#ifdef CONFIG_PROC_FS + remove_proc_entry("pH/status", 0); + remove_proc_entry("pH", 0); +#endif /* CONFIG_PROC_FS */ + +#ifdef CONFIG_MAGIC_SYSRQ + unregister_sysrq_key('z', &sysrq_pH_op); +#endif /* CONFIG_MAGIC_SYSRQ */ + + down(&pH_profile_list_sem); + + profile = pH_profile_list; + while (profile != NULL) { + logfile = profile->seq_logfile; + profile->seq_logfile = NULL; + pH_close_logfile(logfile); + pH_write_profile(profile); + profile = profile->next; + } + + up(&pH_profile_list_sem); + + /* could free the actual profiles, + but that might be dangerous */ + } +} + +void pH_log_status(void) +{ + if (pH_loglevel < PH_LOG_STATE) + return; + + printk(KERN_INFO + "pH: %s\n", pH_version); + printk(KERN_INFO + "pH: monitoring %d, loglevel %d, " + "log_syscalls %d, log_sequences %d, delay_factor %d, " + "normal_factor %u, " + //"mod_min %d, normal_min %d, " + "normal_wait %d, anomaly_limit %d, tolerize_limit %d, " + "suspend_execve %d, suspend_execve_time %d, " + "syscall_count %lu\n", + pH_aremonitoring, pH_loglevel, + (pH_logfile != NULL), + pH_log_sequences, + pH_delay_factor, + pH_normal_factor, + //pH_mod_min, pH_normal_min, + pH_normal_wait, + pH_anomaly_limit, + pH_tolerize_limit, + pH_suspend_execve, + pH_suspend_execve_time, + pH_syscall_count); +} + +void pH_write_all_profiles(void) +{ + pH_profile *profile; + + state("Writing all profiles to disk."); + + down(&pH_profile_list_sem); + + profile = pH_profile_list; + while (profile != NULL) { + pH_write_profile(profile); + profile = profile->next; + } + + up(&pH_profile_list_sem); +} + +/* The pH daemon process calls this to get messages from the kernel, + copied into its own buffer. It blocks if there are no messages to get. +*/ + +int pH_get_message(int arg1) +{ + if (pH_mkdir("/var/lib/pH/profiles/a/b/c")) + printk(KERN_INFO "pH_mkdir worked!"); + else + printk(KERN_INFO "pH_mkdir failed."); + return 0; +} + + +/* called by proc_base_lookup() in fs/proc/base.c */ +int proc_pid_pH_taskinfo(struct task_struct *tsk, char *buffer) +{ + int len, fn_len; + + if (!tsk) + return 0; + + if (tsk->pH_state.profile) { + len = sprintf(buffer, + "normal : %d\n" + "frozen : %d\n" + "delay : %d\n" + "count : %lu\n" + "LFC : %d\n" + "maxLFC : %d\n" + "profile : ", + (tsk->pH_state.profile)->normal, + (tsk->pH_state.profile)->frozen, + tsk->pH_state.delay, + tsk->pH_state.count, + tsk->pH_state.alf.total, + tsk->pH_state.alf.max); + fn_len = strlen((tsk->pH_state.profile)->filename); + if (fn_len > (PAGE_SIZE - len - 2)) + fn_len = PAGE_SIZE - len - 2; + strncpy(buffer + len, (tsk->pH_state.profile)->filename, + fn_len); + len += fn_len; + buffer[len] = '\n'; + buffer[len + 1] = '\0'; + len += 2; + return len; + } else { + return sprintf(buffer, "No profile.\n"); + } +} + +static int pH_proc_status(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len; + + len = sprintf(page, + "pH status\n\n" + "version : %s\n" + "monitoring : %d\n" + "loglevel : %d\n" + "log_syscalls : %d\n" + "log_sequences : %d\n" + "delay_factor : %d\n" + "normal_factor : %u\n" + //"mod_min : %d\n" + //"normal_min : %d\n" + "normal_wait : %d\n" + "anomaly_limit : %d\n" + "tolerize_limit : %d\n" + "suspend_execve : %d\n" + "susp_exec_time : %d\n" + "syscall_count : %lu\n", + pH_version, + pH_aremonitoring, + pH_loglevel, + (pH_logfile != NULL), + pH_log_sequences, + pH_delay_factor, + pH_normal_factor, + //pH_mod_min, + //pH_normal_min, + pH_normal_wait, + pH_anomaly_limit, + pH_tolerize_limit, + pH_suspend_execve, + pH_suspend_execve_time, + pH_syscall_count); + + PH_PROC_READ_RETURN(page,start,off,count,eof,len); +} + +void pH_start(void) +{ +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *ent, *root; + + root = proc_mkdir("pH", 0); + if (root) { + ent = create_proc_entry("status", 0, root); + if (ent) { + ent->read_proc = pH_proc_status; + } + } else { + err("Couldn't create /proc/pH!"); + } +#endif /* CONFIG_PROC_FS */ +#ifdef CONFIG_MAGIC_SYSRQ + register_sysrq_key('z', &sysrq_pH_op); +#endif /* CONFIG_MAGIC_SYSRQ */ + pH_aremonitoring = 1; +} + + +/* + do_pH: function called by sys_pH to actually implement the + pH syscall. This function allows a userspace daemon to + interact with the pH code + + This function is way too long, but really it is just a big case + statement. Maybe I'll split it up someday. + +*/ + + +int pH_cmd_tolerize(int pid) +{ + int result; + struct task_struct *p; + pH_task_state *s; + + p = find_task_by_pid(pid); + if (p) { + s = &(p->pH_state); + if (s->profile) { + result = 0; + down(&s->profile->lock); + pH_stop_normal(s); + up(&s->profile->lock); + state("%d tolerized (%s)", pid, + s->profile->filename); + } else { + result = -EPERM; + state("%d isn't being monitored" + " - can't tolerize", pid); + } + } else { + result = -ESRCH; + state("Can't find %d (to tolerize)", pid); + } + + return result; +} + +int pH_cmd_start_normal(int pid) +{ + int result; + struct task_struct *p; + pH_task_state *s; + + p = find_task_by_pid(pid); + if (p) { + s = &(p->pH_state); + if (s->profile) { + result = 0; + down(&s->profile->lock); + pH_start_normal(s); + up(&s->profile->lock); + /* no need for message - + pH_start_normal alreadly logs one */ + } else { + result = -EPERM; + state("%d isn't being monitored" + " - can't start normal", pid); + } + } else { + result = -ESRCH; + state("Can't find %d (for normal start)", pid); + } + + return result; +} + + +int pH_cmd_sensitize(int pid) +{ + int result; + struct task_struct *p; + pH_task_state *s; + + p = find_task_by_pid(pid); + if (p) { + s = &(p->pH_state); + if (s->profile) { + result = 0; + down(&s->profile->lock); + pH_reset_train(s); + s->profile->anomalies = 0; + up(&s->profile->lock); + state("%d sensitized (%s)", + pid, s->profile->filename); + } else { + result = -EPERM; + state("%d isn't being monitored" + " - can't sensitize", pid); + } + } else { + result = -ESRCH; + state("Can't find %d (to sensitize)", pid); + } + + return result; +} + +int pH_cmd_reset_profile(int pid) +{ + int result; + struct task_struct *p; + pH_task_state *s; + + p = find_task_by_pid(pid); + if (p) { + s = &(p->pH_state); + if (s->profile) { + result = 0; + down(&s->profile->lock); + pH_reset_profile(s); + up(&s->profile->lock); + state("Normal for %d reset (%s)", pid, + s->profile->filename); + } else { + result = -EPERM; + state("%d isn't being monitored" + " - can't reset normal", pid); + } + } else { + result = -ESRCH; + state("Can't find %d (for normal reset)", pid); + } + + return result; +} + + +int pH_cmd_reset_lfc(int pid) +{ + int result; + struct task_struct *p; + pH_task_state *s; + + p = find_task_by_pid(pid); + if (p) { + result = 0; + s = &(p->pH_state); + pH_reset_ALF(s); + state("LFC for %d reset (%s)", pid, + s->profile->filename); + } else { + result = -ESRCH; + state("Can't find %d (for reset LFC)", pid); + } + + return result; +} + +int do_pH(int op, int arg1) +{ + int result = 0; + + /* messing with pH could be equivalent to killing arbitrary + processes, so let's use the kill capability to decide whether + to allow the use of sys_pH */ + if (!capable(CAP_KILL)) { + result = -EPERM; + err("Unauthorized use of sys_pH by %d!", current->pid); + return result; + } + + switch (op) { + case PH_STOPMON: + pH_shutdown(); + state("pH monitoring stopped"); + result = 0; + break; + case PH_STARTMON: + pH_start(); + state("pH monitoring started"); + pH_log_status(); + result = 0; + break; + case PH_DELAYFACTOR: + result = pH_delay_factor; + pH_delay_factor = arg1; + state("delay_factor changed from %d to %d", + result, pH_delay_factor); + break; + case PH_MODMIN: + result = pH_mod_min; + pH_mod_min = arg1; + state("mod_min changed from %d to %d", + result, pH_mod_min); + break; + case PH_NORMALMIN: + result = pH_normal_min; + pH_normal_min = arg1; + state("normal_min changed from %d to %d", + result, pH_normal_min); + break; + case PH_NORMAL_WAIT: + result = pH_normal_wait; + pH_normal_wait = arg1; + state("normal_wait changed from %d to %d", + result, pH_normal_wait); + break; + case PH_ANOM_LIMIT: + result = pH_anomaly_limit; + pH_anomaly_limit = arg1; + state("anomaly_limit changed from %d to %d", + result, pH_anomaly_limit); + break; + case PH_TOLERIZE_LIMIT: + result = pH_tolerize_limit; + pH_tolerize_limit = arg1; + state("tolerize_limit changed from %d to %d", + result, pH_tolerize_limit); + break; + case PH_NORMFACTOR: + result = pH_normal_factor; + pH_normal_factor = arg1; + state("normal_factor changed from %d to %u", + result, pH_normal_factor); + break; + case PH_SUSPEND_EXECVE: + result = pH_suspend_execve; + pH_suspend_execve = arg1; + state("suspend_execve changed from %d to %d", + result, pH_suspend_execve); + break; + case PH_SUSPEND_EXECVE_TIME: + result = pH_suspend_execve_time; + pH_suspend_execve_time = arg1; + state("suspend_execve_time changed from %d to %d", + result, pH_suspend_execve_time); + break; + case PH_LOGLEVEL: + result = pH_loglevel; + pH_loglevel = arg1; + state("debug level changed from %d to %d", + result, pH_loglevel); + break; + case PH_LOG_SEQUENCES: + result = pH_log_sequences; + pH_log_sequences = arg1; + state("log_squences flag changed from %d to %d", + result, pH_log_sequences); + break; + case PH_RESET_PROFILE: + result = pH_cmd_reset_profile(arg1); + break; + case PH_TOLERIZE: + result = pH_cmd_tolerize(arg1); + break; + case PH_STARTSIGNAL: + result = pH_monitorSignal; + pH_monitorSignal = 1; + break; + case PH_STOPSIGNAL: + result = pH_monitorSignal; + pH_monitorSignal = 0; + break; + case PH_START_NORMAL: + result = pH_cmd_start_normal(arg1); + break; + case PH_SENSITIZE: + result = pH_cmd_sensitize(arg1); + break; + case PH_RESET_LFC: + result = pH_cmd_reset_lfc(arg1); + break; + case PH_STATUS: + result = 0; + pH_log_status(); + break; + case PH_LOG_SYSCALLS: + if (arg1 > 0) { + pH_open_syscall_logfile(); + } else { + pH_close_syscall_logfile(); + } + result = 0; + break; + case PH_WRITE_PROFILES: + pH_write_all_profiles(); + result = 0; + break; + case PH_GETMSG: + result = pH_get_message(arg1); + break; + default: + result = -EPERM; + err("No sys_pH option %d!", op); + break; + } + return result; +}