Type-Cast For Structures In Syscall Tracker
-------------------------------------------

Purpose
-------

Various system calls accept polymorphic struct parameters. This means that
the parameter is defined as a pointer to some struct type, but the user may
pass a different struct that overlays on top of the original struct (and
thus, is not larger then the original struct in size). For example, the 'bind'
system call accepts a socket file descriptor as its first parameter, and
a 'struct sockaddr*' as its 2nd parameter. In practice, the user will supply
a 'struct sockaddr_in*' if the socket was created for the 'PF_INET' protocol
family; or the user will supply a 'struct sockaddr_un*', if the socket
was created with the 'PF_UNIX' protocol family, etc. The underlying system
call (or rather, the functions it invokes) is supposed to perform the proper
type cast based on the socket's protocol family.

There are also system calls that accept polymorphic parameters which are not
overlays of a given struct. For example, the 'ioctl' system call accepts
a 'void*' as its 3rd parameter. Later on, based on its first and second
parameters, it decides (or actually, the driver that's supposed to
perform the actual ioctl call) what is the proper type of the last parameter,
casts it, and if its a struct, only then it copies the data from user-space
into kernel-space for processing.

When performing filter matching for such polymorphic structs, we should
perform this based on the actual type of the parameter passed, not based on
the declared type of the syscall's parameter. This will allow referencing
the correct fields inside the structure.


Possible Methods
----------------

Two general methods are possible for this type of casting. One is a static
method, in which the syscall tracker stub function will check the parameters
it received, and will perform the type casting (in direct C source code)
automatically. The advantage of this method is that strong type casting can
performed, leaving less options for the user to supply incorrect rules.
It will also make the actual type casting operation not affect the code
in the 'sct_rules' library, making this change local to the tracker functions
(and the perl script that generates them). The problem with this method is
that it requires us to duplicate large parts of the kernel's code, and even
parts that are spread all over the kernel. This might make it harder to port
the code to new kernel versions. It also makes the syscall tracker stubs
generation less general.

A second method is performing dynamic type casting, by introducing a type
cast filter node type. In this manner, the user is responsible for knowing
how to make the proper type cast (e.g. they know that a given daemon only
works with the PF_INET protocol family, and thus they can do the casting
to 'struct sockaddr_in*'). This makes life a bit more complicated for the
user - yet, this is the case anyway, since they need to know the actual type
of the parameter, in order to reference the proper field(s) in it for
filter matching. The problem with this approach is that the casting operation
is more complicated here.


Implementing The Dynamic Cast Approach
--------------------------------------

The current method of implementing structs is by translating them into vectors
of filter nodes, each of which contains a single field. This means we have
broken down the struct into seperate (disjoint) memory chunks, and since the
filter_node does not contain a pointer to the original struct, we cannot simply
perform a type cast operation on it. What we will have to do is change the
way structs are stored. We will need to store a pointer to the struct, and
a vector of offsets into the struct, one offset per field.

Calculating these offsets can be done in two different methods. The first,
which is employed by existing C language interpreters (such as CiE and CInt), 
is to calculate these offsets ourselves, by knowing how data types are
aligned. The problem is we'll have to mimic the behaviour of the compiler,
which complicates the code.

The second method is by letting the compiler do the work for us. For each
supported struct type (as defined in 'syscalls.dat'), we will generate a
function that calculates the offsets for this structure as follows:

1. create a variable of this struct on the stack.
2. get the address of this variale - this is the base address.
3. for each field in the struct - get its address inside this variable,
   and calculate its offset from the base address. store this offset
   in an offsets vector, and the given index.

This code can be generated by the perl script that generates the tracker stub
functions, and which already parses the structures.


Performing Struct Field Access With Dynamic Types
-------------------------------------------------

If we choose to perform dynamic type casting, and thus store structs using
an offset-vector approach, the code that dereferences such structs will need
to perform the following, assuming it got a variable's type (with optional
index), a field index, and a struct type ID:

1. translate the struct type ID into an offsets vector.
2. translate the struct type ID together with the field index, into a
   data type.
3. extract the data in the given offset, and create a filter_node containing
   the proper data type, and the copy of the actual field.
4. return this newly created filter_node as the value of the variable
   dereference operation.

Note that this implies storing data structures that contain:

1. a map between struct type ID and struct definition data.
2. each stuct definition data will include:
   - number of fields in the struct.
   - struct type as a string (for log printings).
   - vector of field offsets.
   - vector of field types.

These data strucrtures will be initialized during module initialization.


Type-Casting In 'sct_config'
----------------------------

As for 'sct_config' - it need not know about field offsets, since it never
performs struct dereference. The change it will need, is supporting a
type-cast directive, which will simply tell it to replace the struct type
with another struct type, and then keep on with its former method of type
checking - using this new struct type.

