System Calls Tracking Configuration Utility - Design

$Id: sct_config_design.html,v 1.6 2001/09/27 09:32:15 mulix Exp $

TOC

  1. Top-Level Design
  2. The Workflow
  3. The Parser
  4. The Rule Structure
  5. The Conditions Structures
  6. Communications with the Module
  7. The Configuration Language


Top-Level Design

What is the system call tracker configuration utility?

The system call tracker is made of two parts: a kernel module which intercepts, monitors and potenitally takes action upon a system call invocation, and a configuration utility. The configuration utility is used to configure the module, informing it what system calls to monitor and what actions to take, if necessary. The user specifies the configuration in a speciall configuration file. The configuration utility first parses and validates this file and then communicates the user's wishes to the module.


The Workflow

The configuration utility is invoked from the command line. It accepts a command (print current kernel rules, delete current kernel rules, check configuration and update kernel configuration) and potentially a configuration file name.

  1. The config file is checked, to make sure it is secure. (That it is not a symlink, that only root has write access to it, etc).
  2. The configuration file is loaded and parsed into a set or rules. During parsing we check for both syntactic errors (errors in the form and syntax of the configuration file).
  3. The rules are validated- checked for semantic errors (a rule which requests an invalid filter- tracking a system call with two parameters based on a third parameter)
  4. If all rules are valid, they are serialized and communicated to the module

The Parser

The parser has one entry point: parse(). Internally, the parser parses its input into tokens. There are two types of tokens, a simple token and a complex token.

A simple token is made of the pair "name:value", where name is one of the predefined identifiers, and value is of a type matching to that predefined identifier.

A complex token is made of a "name { ... }" sequence. That is the name of this complex token ("rule", "process condition", etc) an opening brace, a set of tokens, and closing brace.

After the parser built a token, it does one of two things: if it's a simple token, an object is built in memory representing this entity - a process conditinoal, a rule, etc. if it's a complex token, the token is forwarded to an another parse function which knows how to parse tokens of this type.

For more information, see the source, especially parser.h and parser.cpp.

The parser currently builds an intermediate representation of the configuration, using internal parser data members and types. These types closely model the Rules Engine data types - it is conceivable that the parser could build the configuration as Rule Engine types directly, instead of using an intermediate representation.


The Rule Strcuture

The rule structure has several data members, such as the rule's name, the rule's internal id, the action to take if this rule matches, etc. It also has two lists, one of condions based upon the system call parametersm, and one based upon the process parameteres.


The Conditions Structures

There are two kinds of conditions available. One is based upon the system call parameters ("log all 'open' system calls whose first parameter is '/etc/passwd'") and one is based upon process parameters ("log all 'open' system calls whose process EUID is 0 (root)"


Communicating with the Module

Communications with the module are done using the sct_ctrl_lib library.


The Configuration Language

the config file will be self documenting. any line that starts with '#' or a '//' or a '/*' is a comment.

a rule is written like this:
rule{
	//what is the rule's name?
	rule_name:STRING

	//what syscall does this rule work on?
	syscall_name:SYSCALL_NAME

	//the action to take
	action:ACTION

	conditions:CONDITION_MATCHING

	//a rule can have 0 or more conditional clauses

	//a conditional clause based on a syscall param
	param_condition{
		param_location:INT
		operator:OPERATOR 
		value:VALUE
	}

	//a condition based on the process calling the syscall
	process_condition{
		process_field:PROCESS_FIELD 
		operator:OPERATOR
		value:LONG
	}
}


STRING: a literal string (quotes aren't necessary)

SYSCALL_NAME: the name of the syscall.

ACTION: LOG, SUSPEND, FAIL, KILL

CONDITION_MATCHING: can be either 'ANY' or 'ALL'

INT: an integer value, must be specified in decimal.

OPERATOR:"==", "<", ">", "<=", ">=", "!=", "&", "|", "^", "!"

VALUE: can represent each of the above data types.
       for 'b' (bool) use 'true' or 'false'
       for str see STRING

PROCESS_FIELD: UID, EUID, GID, EGID, PID, PPID, COMM