Most applications communicate with the operating system by way of a standard library which converts their procedure calls into appropriate kernel operations:
An interposition agent is a small piece of software which transforms a program by inserting itself between an application and the operating system. There are many different mechanisms and layers for accomplishing this. Bypass can insert an agent between the application and the standard library like so:
Such an agent has many different possible missions:
Bypass makes programming agents easy by hiding all of the implementation details behind an intuitive language. This language allows the agent to hide certain procedures from the application and then invoke them selectively. For example, to change the behavior of open, the agent programmer may write:
int open( in string const char *path, int flags, int mode )
agent_action
{{
if( !strncmp(path,"/etc",4) ) {
fprintf(stderr,"Operation denied.\n");
errno = EPERM;
return -1;
} else {
return open( path, flags, mode );
}
}};
Notice that there are some interesting scoping rules going on here. The definition of open given here is used by the application in every place where it calls open. However, the code given above also references open. That call is bound to the definition of open given in the standard library. In general, the binding of procedure calls to procedure definitions only flows down.
A split execution system is a special case of an interposition agent. In such a system, an interposition agent traps some of an application's procedure calls, and may conditionally forward them (via RPC) to a shadow process on another machine.
int open( in string const char *path, int flags, int mode )
agent_action
{{
if( !strncmp(path,"/etc",4) ) {
fprintf(stderr,"Operation denied.\n");
errno = EPERM;
return -1;
} else {
return bypass_shadow_open( path, flags, mode );
}
}}
shadow_action
{{
fprintf(stderr,"application opened %s\n",path);
return open(path,flags,mode);
}};
Why would you want to do such a strange thing? In a distributed system such as Condor or Globus, you may be able to distribute your jobs to a wide variety of free machines. However, an arbitrary machine plucked from the internet may not have the resources that you need to execute. Most likely, it will not have the input files (or output space) to run your job. Using a split execution system, your program can make use of the remote CPU cycles while still getting correct results for system calls that must be executed at your home machine.
Bypass inserts interposition agents using the "preloading" feature found on most UNIX-like operating systems. This is as simple as setting an environment variable:
setenv LD_PRELOAD `pwd`/info_agent.so
So, when Bypass "traps" a call, it is actually jsut providing a new definition of the old procedure. This makes "trapping" calls very fast. For every agent inserted between the application and the library, a trapped call is only slowed down by time comparable to a plain procedure call.
Of course, Bypass has some limitations. Because it relies on the dynamic linker to insert agents, it may only be applied to dynamically linked programs. To conserve space, most UNIX applications are dynamically linked, however, there are a few codes (mostly commercial) that remain statically linked. For this same reason, it is only suitable as a programming convenience, and not as a security mechanism. To really enforce a security policy, a slower (but more secure) kernel-level policy is needed.