`
`Kevin Scott and Jack Davidson
`
`Department of Computer Science, University of Virginia
`Charlottesville, VA 22904
`{kscott, jwd}@cs.virginia.edu
`
`Abstract
`Safe virtual execution (SVE) allows a host computer
`system to reduce the risks associated with running
`untrusted programs. SVE prevents untrusted programs
`from directly accessing system resources, thereby giv-
`ing the host the ability to control how individual
`resources may be used. SVE is used in a variety of
`safety-conscious software systems, including the Java
`Virtual Machine (JVM), software fault isolation (SFI),
`system call interposition layers, and execution moni-
`tors. While SVE is the conceptual foundation for these
`systems, each uses a different implementation technol-
`ogy. The lack of a unifying framework for building SVE
`systems results in a variety of problems: many useful
`SVE systems are not portable and therefore are usable
`only on a limited number of platforms; code reuse
`among different SVE systems is often difficult or impos-
`sible; and building SVE systems from scratch can be
`both time consuming and error prone.
`To address these concerns, we have developed a por-
`table, extensible framework for constructing SVE sys-
`tems. Our framework, called Strata, is based on
`software dynamic translation (SDT), a technique for
`modifying binary programs as they execute. Strata is
`designed to be ported easily to new platforms and to
`date has been targeted to SPARC/Solaris, x86/Linux,
`and MIPS/IRIX. This portability ensures that SVE
`applications implemented in Strata are available to a
`wide variety of host systems. Strata also affords the
`opportunity for code reuse among different SVE appli-
`cations by establishing a common implementation
`framework.
`Strata implements a basic safe virtual execution
`engine using SDT. The base functionality supplied by
`this engine is easily extended to implement specific SVE
`systems. In this paper we describe the organization of
`Strata and demonstrate its extension by building two
`SVE systems: system call interposition and stack-
`smashing prevention. To illustrate the use of the system
`call interposition extensions, the paper presents imple-
`mentations of several useful security policies.
`
`1. Introduction
`
`Today’s software environment is complex. End users
`acquire software from a number of sources, including
`the network, and have very little on which to base their
`trust that the software will correctly perform its
`intended function. Given the size of modern software—
`operating system kernels are comprised of millions of
`lines of source code and application programs are often
`an order of magnitude larger—it is difficult or impossi-
`ble for developers to guarantee that their software is
`worthy of the end user’s trust. Even if developers could
`make such guarantees about the software they distrib-
`ute, hostile entities actively seek to modify that soft-
`ware to perform unanticipated, often harmful functions
`via viruses and Trojan horses.
`In recent years, researchers have developed a variety
`of techniques for managing the execution of untrusted
`code. These techniques can be divided into two orthog-
`onal categories: static and dynamic. Static techniques
`analyze untrusted binaries before execution to deter-
`mine whether or not the program is safe to run. Proof
`carrying code [17] is a good example of the static
`approach—before a program can execute, the runtime
`system must successfully validate a proof that the
`untrusted binary will adhere to a given safety policy.
`Many static approaches, including proof carrying code,
`rely on source code analyses to produce safe binaries
`[5,15,22]. Dynamic techniques, on the other hand, do
`not require access to source code. Rather, dynamic
`techniques prevent violation of safety policies by moni-
`toring and modifying the behavior of untrusted binaries
`as they execute. An example of a dynamic approach is
`execution monitoring [9,18]. Execution monitors termi-
`nate the execution of a program as soon as an imper-
`missible sequence of events (corresponding to a safety
`policy violation) is observed. System call interposition
`layers [11, 12, 13, 14] are similar to execution monitors
`with the additional ability to alter the semantics of
`events, specifically system calls. Yet another similar
`dynamic
`technique, software fault
`isolation (also
`
`Proceedings of the 18th Annual Computer Security Applications Conference (ACSAC(cid:146)02)
`1063-9527/02 $17.00 ' 2002 IEEE
`
`000001
`
`Symantec 1011
`IPR of U.S. Pat. No. 7,757,289
`
`
`
`known as sandboxing) [23] limits the potential damage
`an untrusted binary can do by preventing loads, stores,
`or jumps outside of a restricted address range.
`In this paper we make the following observation:
`many dynamic trust management systems, including
`the ones mentioned above, can be implemented using a
`technique called safe virtual execution (SVE). SVE
`mediates application execution, virtualizing access to
`sensitive resources in order to prevent untrusted bina-
`ries from causing harm. Despite the fact that SVE pro-
`vides a conceptual framework for the implementation
`of systems such as execution monitors, interposition
`layers, and sandboxing, these systems are frequently
`based on widely differing implementation technologies.
`These systems are often dependent on a specific target
`architecture or on special operating system services,
`hence impeding their widespread use in the modern het-
`erogeneous networked computing environment. In
`addition to non-portability, the use of different imple-
`mentation technology places undue engineering bur-
`dens on the designers of SVE systems. They cannot
`share code and features with similar systems and must
`often endure the time consuming and error-prone chore
`of building their systems from scratch.
`To address these concerns, we have developed a por-
`table, extensible framework for constructing SVE sys-
`tems. Our framework, called Strata, is based on
`software dynamic translation (SDT), a technique for
`modifying binary programs as they execute [1, 2, 3, 6,
`7, 20, 21, 24]. Using SDT, Strata offers a basic safe vir-
`tual execution engine. The base functionality supplied
`by this engine can be extended in order to implement
`specific SVE systems. Using this approach useful SVE
`systems can often be implemented with very few lines
`of new code. Strata is designed to be easily ported to
`new platforms and to date has been targeted to SPARC/
`Solaris, x86/Linux, and MIPS/IRIX. This portability
`ensures that SVE applications implemented in Strata
`are available to a wide variety of host systems. Strata
`also affords the opportunity for code reuse among dif-
`ferent SVE applications by establishing a common
`implementation framework.
`The remainder of this paper is organized as follows.
`Section 2 provides an overview of software dynamic
`translation and Section 3 describes Strata’s organization
`and architecture. Section 4 then describes how Strata is
`used to implement a system call interposition layer and
`how this layer can be used to implement powerful secu-
`rity policies. Section 5 discusses our results while Sec-
`tion 6 discusses related work, and Section 7 provides a
`summary.
`
`2. Software Dynamic Translation
`
`SDT is a technique for dynamically modifying a
`program as it is being executed. Software dynamic
`translation has been used in a variety of different areas:
`binary translation for executing programs on non-native
`CPUs [6, 7, 21]; fast machine simulation [3, 24]; and
`recently, dynamic optimization [1]. In this paper we
`describe how software dynamic translation can be used
`to implement safe virtual execution.
`Most software dynamic translators are organized as
`virtual machines (see Figure 1a). The virtual machine
`fetches instructions, performs an application-specific
`translation to native instructions, and then arranges for
`the translated instructions to be executed. Safe virtual
`execution systems can be viewed as types of virtual
`machines. On a conceptual level, an SVE virtual
`machine prevents untrusted binaries from directly
`manipulating system resources. The difference between
`SVE systems is in how this virtual machine is imple-
`mented. For instance, in the Java Virtual Machine an
`interpreter is used to isolate Java bytecode programs
`from underlying system resources [16]. Systems such
`as SASI [9] and SFI [23] merge the application program
`with the SVE virtual machine, using binary rewriting at
`load time; the virtual machine is in the form of instruc-
`tions that check certain sequences of instructions before
`they are allowed to execute. Systems such as Janus [13]
`and Interposition Agents [14] use special operating sys-
`tem facilities to virtualize the execution of a very spe-
`cific aspect of execution, specifically, system calls.
`In this paper we propose the use of software
`dynamic translation as the basis for implementing safe
`virtual execution systems. Implementing an SVE appli-
`cation in a software dynamic translator is a simple mat-
`ter of overriding the translator’s default behavior. For
`example, an SDT implementation of a software fault
`isolator would
`translate
`load
`instructions
`into a
`sequence of instructions that performs an address check
`before the load executes.
`In order to illustrate our approach in brief, consider
`the task of preventing stack-smashing attacks using
`SDT. Stack-smashing attacks take advantage of unsafe
`buffer manipulation functions (e.g., strcpy from the C
`standard library) to copy, and subsequently execute,
`malicious code from the application stack. The mali-
`cious code is executed with the privileges of the user
`running the program, and in many cases can lead to
`serious security compromises on affected systems
`[4,15].
`A simple way to prevent stack-smashing attacks is to
`make the application stack non-executable. In the
`abscence of operating system support for non-execut-
`able stacks, it is a trivial matter to prevent execution of
`
`Proceedings of the 18th Annual Computer Security Applications Conference (ACSAC(cid:146)02)
`1063-9527/02 $17.00 ' 2002 IEEE
`
`000002
`
`
`
`SDT Virtual Machine
`
`Application
`
`Context
`Capture
`
`New
`PC
`
`Cached?
`
`Yes
`
`Context
`Switch
`
`Finished?
`
`Yes
`
`Strata Virtual
`
`Machine
`
`Context Management
`
`Linker
`
`Memory Management
`
`Strata Virtual CPU
`
`Cache Management
`Target Interface
`Target Specific Functions
`
`New
`Fragment
`
`Fetch
`
`Decode
`
`Translate
`Next PC
`
`No
`
`Host CPU (Executing Translated Code from Cache)
`
`(a)
`
`Figure 1: Strata Architecture
`
`Host CPU
`
`(b)
`
`code on the stack by using SDT. This task is accom-
`plished by replacing the software dynamic translator’s
`default fetch function with a custom fetch that prevents
`execution of stack resident code.
`The custom fetch function
`
`custom_fetch (Address PC) {
`if (is_on_stack(PC)) {
`fail("Cannot execute code on the
`stack");
`} else {
`return default_fetch(PC);
`
`}
`
`}
`checks the PC against the stack boundaries and termi-
`nates program execution if the instruction being fetched
`is on the stack. If the instruction being fetched is not on
`the stack, it is alright to execute the instruction, and
`consequently the fetch is completed by calling the
`default fetch function.
`
`3. Strata
`
`To facilitate SDT research and the development of
`innovative SDT applications, we have constructed a
`portable, extensible SDT infrastructure called Strata. As
`shown in Figure 1a, Strata is organized as a virtual
`machine. The Strata VM mediates application execu-
`tion by examining and translating instructions before
`they execute on the host CPU. Translated instructions
`are held in a Strata-managed cache. The Strata VM is
`entered by capturing and saving the application context
`(e.g., PC, condition codes, registers, etc.). Following
`context capture, the VM processes the next application
`instruction. If a translation for this instruction has been
`cached, a context switch restores the application context
`
`and begins executing cached translated instructions on
`the host CPU.
`If there is no cached translation for the next applica-
`tion instruction, the Strata VM allocates storage for a
`new fragment of translated instructions. A fragment is a
`sequence of code in which branches may appear only at
`the end. The Strata VM then populates the fragment by
`fetching, decoding, and translating application instruc-
`tions one-by-one until an end-of-fragment condition is
`met. The end-of-fragment condition is dependent on the
`particular software dynamic translator being imple-
`mented. For many translators, the end-of-fragment con-
`dition is met when an application branch instruction is
`encountered. Other translators may form fragments that
`emulate only a single application instruction. In any
`case, when the end-of-fragment condition is met, a con-
`text switch restores the application context and the
`newly translated fragment is executed.
`As the application executes under Strata control,
`more and more of the application’s working set of
`instructions materialize in the fragment cache. This,
`along with certain other techniques—e.g., partial inlin-
`ing of functions and indirect branch elimination—that
`reduce the number and cost of context switches, permits
`Strata to execute applications with little or no measur-
`able overhead [19].
`Figure 1b shows the components of the Strata VM.
`Strata was designed with extensibility and portability in
`mind. Extensibility allows Strata to be used for a vari-
`ety of different purposes; researchers can use Strata to
`build dynamic optimizers, dynamic binary translators,
`fast architecture emulators, as well as safe virtual exe-
`cution systems. Portability allows Strata to be moved to
`new machines easily. To date, Strata has been ported to
`SPARC/Solaris, x86/Linux, and MIPS/IRIX. More
`
`Proceedings of the 18th Annual Computer Security Applications Conference (ACSAC(cid:146)02)
`1063-9527/02 $17.00 ' 2002 IEEE
`
`000003
`
`
`
`importantly, Strata’s portability means that software
`implemented using Strata’s extensibility features is
`readily available on a wide range of target architectures
`and operating systems.
`To achieve these goals, the Strata virtual machine is
`implemented as a set of target-independent common
`services, a set of target-specific functions, and a recon-
`figurable target interface through which the machine-
`independent and machine-dependent components com-
`municate (see Figure 1b). Implementing a new software
`dynamic translator often requires only a small amount
`of coding and a simple reconfiguration of the target
`interface. Even when the implementation is more
`involved, e.g., when retargeting the VM to a new plat-
`form, the programmer is only obligated to implement
`the target-specific functions required by the target inter-
`face; common services should never have to be reim-
`plemented or modified.
`Strata consists of 5000 lines of C code, roughly half
`of which is target-specific. In Figure 1b, shaded boxes
`show the Strata common services which comprise the
`remaining half of the Strata source. The Strata common
`services are target-independent and implement func-
`tions that may be useful in a variety of Strata-based
`dynamic translators. Features such as context manage-
`ment, memory management, and the Strata virtual CPU
`will most likely be required by any Strata-based
`dynamic translator. The cache manager and the linker
`can be used to improve the performance of Strata-based
`dynamic translators, and are detailed in other work
`[19].
`
`4. Strata and Safe Virtual Execution
`
`In Section 2 we sketched one example that demon-
`strates the process one can use to write a Strata-based
`safe virtual execution system, specifically, a stack-
`smashing inhibitor. In this section we use Strata to
`implement a system call interposition layer. This inter-
`position layer, like all Strata-based applications, is user-
`level software and requires no kernel modifications.
`Our Strata-based system call interposition layer also
`obviates the need for special operating system services
`for interception or redirection of system calls. As a con-
`sequence, our system call interposition layer is more
`flexible and portable than many existing systems.
`SDT’s ability to control and dynamically modify a
`running program provides an ideal mechanism for
`implementing a system call interposition layer. As the
`untrusted binary is virtualized and executed by Strata,
`code is dynamically inserted to intercept system calls
`and potentially redirect those calls to user supplied
`functions. In general though, this process does not need
`to be limited to system calls; all access to host CPU and
`
`operating system resources are explicitly controlled by
`Strata (see Figure 2).
`
`Untrusted
`Binary
`
`Host CPU
`
`Strata SVE Application
`
`Host CPU and OS
`Services
`
`Figure 2: Strata
`
`In this paper, we will use terms and phrases that are
`typically employed when discussing the Unix operating
`system (e.g., “becoming root”, “exec’ing a shell”, “per-
`forming a setuid(0)”, etc.). The actions indicated by
`these terms have analogs in other major operating sys-
`tems (e.g., Windows NT, Windows 2000, Window XP,
`VxWorks, and PSOSystem) and the approaches we
`describe would apply equally well to applications run-
`ning on these systems.
`A simple, but realistic example illustrates our
`approach. Suppose a user wishes to enforce a policy
`that prohibits untrusted applications from reading a file
`that the user normally has permission to read. Let’s call
`this file /etc/passwd (registry.dat, SAM, or sys-
`tem might be equally good choices). Now assume that
`the user receives an untrusted binary called funny and
`wishes to run it. The user invokes funny using the
`Strata loader. The Strata loader locates the entry point
`of the application and inserts a call to the Strata startup
`routine. When the loader begins the execution of the
`application, the call to the Strata startup routine leads to
`the dynamic loading and invocation of Strata.
`As Strata processes funny’s text segment and builds
`fragments to be executed, it locates open system calls
`and replaces them with code that invokes the execution
`steering policy code. When the fragment code is exe-
`cuted, all open system calls are diverted to the policy
`code. It is the policy code’s job to examine the argu-
`ments to the original open system call. If the untrusted
`application is attempting to open /etc/passwd, an
`error message is issued and the execution of the appli-
`cation is terminated. If the file being opened is not /
`etc/passwd, the security policy code performs the
`
`Proceedings of the 18th Annual Computer Security Applications Conference (ACSAC(cid:146)02)
`1063-9527/02 $17.00 ' 2002 IEEE
`
`000004
`
`
`
`open request, returns the result, and execution contin-
`ues normally (albeit under the control of Strata).
`
`20.
`21. int main(int argc, char *argv[]) {
`22.
`FILE *f;
`
`4.1. A System Call Interposition API
`
`We support system call interposition through an API
`implemented by overriding Strata’s base functionality.
`The API is a simple, efficient mechanism that allows
`the user to specify which operating system calls are to
`be monitored and the code to execute every time the
`operating system call is invoked. Strata’s execution
`steering API consists of four functions. They are:
`void init_syscall();
`watch_syscall(unsigned num, void *callback);
`void strata_policy_begin(unsigned num);
`void strata_policy_end(unsigned num);
`The first function is called on the initial entry to Strata.
`The implementation of this function will contain calls
`to the second API function watch_syscall(). Func-
`tion watch_syscall() specifies an operating system
`call to watch (i.e., num) and the redirected system call to
`execute when that OS call is invoked (i.e., callback).
`The signature of callback should match the signature
`of the operating system call being watched. The final
`two API functions are used to bracket redirected system
`call code. The need for the bracketing functions will be
`explained shortly when we describe how Strata dynam-
`ically injects code into the application.
`
`To illustrate the implementation of Strata’s security
`API, we show the Strata security policy for preventing
`an untrusted application from reading /etc/passwd.
`Following the style used on hacker websites to demon-
`strate the exploitation of security vulnerabilities, we
`give a small demonstration program that exercises the
`policy. The demonstration code is given in Listing 1.
`1. #include <stdio.h>
`2. #include <string.h>
`3. #include <strata.h>
`4. #include <sys/syscall.h>
`
`5. int myopen (const char *path, int oflag) {
`6.
`char absfilename[1024];
`7.
`int fd;
`
`8.
`
`9.
`10.
`11.
`12.
`13.
`
`14.
`
`strata_policy_begin(SYS_open);
`
`makepath_absolute(absfilename,path,1024);
`if (strcmp(absfilename,"/etc/passwd") == 0) {
`strata_fatal("Naughty, naughty!");
`
`}
`fd = syscall(SYS_open, path, oflag);
`
`strata_policy_end(SYS_open);
`
`return fd;
`
`15.
`16. }
`17. void init_syscall() {
`18.
`(*TI.watch_syscall)(SYS_open, myopen);
`19. }
`Listing 1: Code for preventing a file from being
`opened.
`
`23.
`
`24.
`25.
`26.
`
`27.
`
`if (argc < 2 || (f = fopen(argv[1],"r")) ==
`NULL) {
`fprintf(stderr,"Can't open file.\n");
`exit(1);
`
`}
`
`printf("File %s opened.\n",argv[1]);
`
`return 0;
`
`28.
`29. }
`Listing 1: Code for preventing a file from being
`opened.
`
`Before explaining how Strata injects this code into
`an untrusted binary, we review the code at a high level.
`Function init_syscall() at lines 17–19 specifies
`that SYS_open calls should be monitored and that when
`a SYS_open call is to be executed by the application,
`control is to be transferred to the policy routine myo-
`pen().
`Function myopen() (lines 5–16) implements the
`redirected system call. As mentioned previously, invo-
`cations
`of
`strata_policy_begin()
`and
`strata_policy_end() are used to bracket the redi-
`rected system call code and their purpose will be
`explained shortly.
`In function myopen(), the path to be opened is con-
`verted to an absolute pathname by calling the utility
`function makepath_absolute(). The path returned is
`compared to the string /etc/passwd and if it matches,
`an error message is issued and execution is terminated.
`If the file to be opened is not /etc/passwd, then the
`policy code performs the SYS_open system call and
`returns the result to the client application as if the actual
`system call was executed.
`When an untrusted binary is to be executed, the
`Strata loader modifies the application binary so that ini-
`tial control is transferred to Strata’s initialization rou-
`tines. This routine dynamically loads and executes the
`init_syscall() function that sets up a table of sys-
`tem calls to watch and their corresponding callback
`functions.
`After initialization is complete, Strata begins build-
`ing the initial application fragment by fetching, decod-
`ing and translating instructions from the application
`text into the fragment cache. The system call interposi-
`tion API is implemented by overriding the translate
`function that handles trap or interrupt instructions. For
`the SPARC/Solaris platform, less than 20 lines of code
`are required to implement the new translation function-
`ality.
`Strata examines each operating system call site to
`determine if the OS call is one to be monitored. In most
`cases, Strata can determine at translation time which
`operating system call will be invoked at the call site. If
`
`Proceedings of the 18th Annual Computer Security Applications Conference (ACSAC(cid:146)02)
`1063-9527/02 $17.00 ' 2002 IEEE
`
`000005
`
`
`
`the OS call is one to be monitored, the code to invoke
`the operating system call is replaced with a call to the
`user-supplied code. If the call is not one to be moni-
`tored, no translation action needs to be taken and the
`operating system call code is copied unchanged to the
`fragment cache.
`In some cases, Strata cannot determine which oper-
`ating system call will be invoked at a given call site.
`This can occur, for instance, with indirect operating
`system calls. In these cases, Strata must generate and
`insert code that, when the fragment is executed, will
`test whether the OS call being invoked is one to be
`monitored. If the call is one to be monitored, the
`inserted code must call the appropriate user-supplied
`policy code; otherwise, the OS call is executed.
`In the case where the OS call to be invoked can be
`determined at fragment creation (translation) time,
`Strata treats redirected code just like application code.
`As a result, calls to redirected system call code can
`often be partially inlined [1, 19], thus improving the
`efficiency of the code. However, partially inlining code
`creates a complication. Consider the myopen() code in
`Listing 1. When this code is inlined, the SYS_open OS
`call will be generated. This OS call should not be
`replaced by a callback, as it is the OS call to execute
`when the policy’s conditions are satisfied. To avoid
`infinite recursion, redirected system calls are bracketed
`using
`the
`interposition
`API
`calls
`strata_policy_begin()
`and
`strata_policy_end(). Strata uses
`these “code
`markers” to suspend the translation of operating system
`calls. Thus, we are assuming that the writer of policy
`code is not malicious.
`One further complication exists. A malicious user
`with knowledge of how Strata operates may try to cir-
`cumvent
`Strata
`by
`using
`calls
`to
`strata_policy_begin()
`and
`strata_policy_end() to bracket application code
`that attempts to violate the security policy. To prevent
`this
`avenue
`of
`attack,
`Strata
`permits
`strata_policy_begin()
`and
`strata_policy_end() to execute only from within
`security policy code.
`
`4.2. System Call Interposition at Work
`
`A common security exploit is to arrange to exec a
`shell while in root or super-user mode. This is most
`commonly done by using a buffer overrun attack that
`corrupts the run-time stack. In an earlier paper we
`described how such an attack can be stopped using
`Strata’s target-dependent interfaces [13]. Other types of
`attacks are possible [16]. However, they all rely on
`
`exec’ing a program (usually a shell) while in root or
`super-user mode. Using Strata’s security API, it is very
`simple to write a policy that prohibits exec’ing a pro-
`gram when in super-user mode, yet allows exec’s when
`not in super-user mode. Listing 2 contains the demon-
`stration program.
`1. #include <stdio.h>
`2. #include <string.h>
`3. #include <unistd.h>
`4. #include <strata.h>
`5. #include <sys/syscall.h>
`
`6. static int curuid = -1;
`7. int mysetuid (int uid) {
`8.
`strata_policy_begin(SYS_setuid);
`9.
`curuid = syscall(SYS_setuid, uid);
`10.
`strata_policy_end(SYS_setuid);
`11.
`return curuid;
`12. }
`
`13. int myexecve (const char *path, char *const
`argv[],
`14. char *const envp[]) {
`15.
`int retval;
`16.
`strata_syscallback_begin(SYS_execve);
`17.
`if (curuid == 0)
`18.
`strata_fatal(“Naughty, naughty”);
`19.
`retval = syscall(SYS_execve, path, argv,
`envp);
`strata_syscallback_end(SYS_execve);
`return retval;
`
`20.
`21.
`22. }
`
`23. void init_syscall() {
`24.
`(*TI.watch_syscall)(SYS_execve, myexecve);
`25.
`(*TI.watch_syscall)(SYS_setuid, mysetuid);
`26. }
`
`27. int main (int argc, char *argv[]) {
`28.
`FILE *f;
`29.
`char *args[2] = {“/bin/sh”,0};
`30.
`setuid(0);
`31.
`execv(“/bin/sh”, args);
`32.
`return 0;
`33. }
`Listing 2: Code to prevent exec’s while root.
`
`Two system calls—setuid and execve—must be
`monitored to implement this security policy. We must
`monitor setuid to keep track of the uid of the running
`application. This information is stored in the state vari-
`able curuid. In function myexecve(), exec’s are dis-
`allowd if the program is running in root mode (i.e., the
`uid of the process is 0); otherwise they are allowed.
`This example demonstrates a number of advantages
`of our system call interposition API. It is easy to see
`that the code required to implement the security policy
`using the system call interposition API is simple and
`straightforward. We do not rely on special operating
`system services, compilers, or libraries. The user does
`not have to learn a new domain specific language in
`order to write security policies. Furthermore, the use of
`C as the security policy language does not imply that
`untrusted binaries must be written in C. The security
`policy is compiled to binary code that is processed by
`
`Proceedings of the 18th Annual Computer Security Applications Conference (ACSAC(cid:146)02)
`1063-9527/02 $17.00 ' 2002 IEEE
`
`000006
`
`
`
`Strata along with the untrusted binary. The security pol-
`icy code is portable to most systems with a native C
`compiler and POSIX compliant system calls. Moreover,
`static source code analysis cannot effectively prevent
`execs while root due to a number of inhibiting factors—
`unavailability of library source code, dynamically gen-
`erated code, self-modifying code, and the inability of
`static analyses to precisely predict dynamic state.
`The third security policy presented implements a
`policy that controls the rate at which an application uses
`a resource. In this example, we will limit the rate at
`which an application can transmit packets over a
`socket. This type of policy could be useful for thwarting
`denial of service attacks where zombie processes
`attempt to flood a server with packets. Listing 3 gives
`the code for the demonstration application.
`1. #include <stdio.h>
`2. #include <stdlib.h>
`3. #include <sys/types.h>
`4. #include <sys/socket.h>
`5. #include <netinet/in.h>
`6. #include <netdb.h>
`7. #include <time.h>
`8. #include <string.h>
`9. #include <strata.h>
`10. #include <sys/syscall.h>
`
`11. #define RATE 10000
`12. #define TOPRATE 10000000
`13. #define DISCARD_PORT 9999
`14. #define PAYLOAD_SIZE 1024
`
`15. void xmit (const char *host, int nbytes);
`
`16. static int socket_fd = -1;
`
`17. /* Compute the delay necessary to maintain */
`18. /* the desired rate */
`19. int limiting_delay (double rate, time_t tbeg,
`20. time_t tend, int last_len, int len);
`
`21. /* Callback for the so_socket call */
`22. int my_so_socket (int a,int b,int c,char *d,int e)
`{
`
`23.
`
`24.
`25.
`26.
`
`27.
`28.
`29. }
`
`strata_policy_begin(SYS_so_socket);
`
`/* Make the system call and */
`/* record the file descriptor */
`socket_fd = syscall(SYS_so_socket,a,b,c,d,e);
`
`strata_policy_end(SYS_so_socket);
`return socket_fd;
`
`30. /* Callback for the write system call */
`31. int my_send (int s, const void *msg, size_t len,
`32. int flags) {
`33.
`int result;
`34.
`time_t now;
`35.
`static int last_len = 0;
`36.
`static time_t last_time = 0;
`
`37.
`
`38.
`39.
`40.
`41.
`42.
`
`strata_policy_begin(SYS_send);
`
`/* Only look at writes to socket_fd */
`if (s == socket_fd) {
`now = time(NULL);
`sleep(limiting_delay(RATE,last_time, now
`len,last_len));
`
`Listing 3: Code to limit the rate of
`transmission over a socket.
`
`43.
`44.
`45.
`46.
`
`47.
`48.
`49. }
`
`last_len = len;
`last_time = now;
`
`}
`result = syscall(SYS_send,s,msg,len,flags);
`
`strata_policy_end(SYS_send);
`return result;
`
`50. void init_syscall() {
`51.
`
`(*TI.watch_syscall)(SYS_so_socket,my_so_socket);
`(*TI.watch_syscall)(SYS_send,my_send);
`
`52.
`53. }
`
`54. main(int argc, char *argv[]) {
`
`55.
`56.
`57.
`58.
`59.
`60. }
`
`if (argc == 3)
`xmit(argv[1],atoi(argv[2]));
`else
`fprintf(stderr,
`”Usage: %s host nbytes\n”,argv[0]);
`
`61. /* Transmit nbytes to discard port (9) on host */
`62. void xmit (const char *host, int nbytes) {
`63.
`int sd, bytes_sent;
`64.
`struct sockaddr_in sin;
`65.
`struct sockaddr_in pin;
`66.
`struct hostent *hp;
`67.
`char *payload[PAYLOAD_SIZE];
`68.
`time_t begin, elapsed;
`69.
`double rate;
`
`70.
`
`/* go find out about the desired host machine
`
`*/
`
`*/
`
`if ((hp = gethostbyname(host)) == 0) {
`perror(“gethostbyname”);
`exit(1);
`
`}
`
`/* fill in the socket structure with host info
`
`memset(&pin, 0, sizeof(pin));
`pin.sin_family = AF_INET;
`pin.sin_addr.s_addr = ((struct in_addr *)
`(hp->h_addr))->s_addr;
`pin.sin_port = htons(DISCARD_PORT);
`
`/* grab an Internet domain socket */
`if ((sd = socket(AF_INET,SOCK_STREAM, 0)) == -
`1) {
`
`perror(“socket”);
`exit(1);
`}
`
`/* connect to PORT on HOST */
`if (connect(sd, (struct sockaddr *) &pin,
`sizeof(pin)) == -1) {
`perror(“connect”);
`exit(1);
`
`}
`
`begin = time(0);
`bytes_sent = 0;
`while(bytes_sent < nbytes) {
`/* send a message to the server PORT */
`/* on machine HOST */
`if (send(sd,payload,sizeof(payload),0) == -
`
`71.
`72.
`73.
`74.
`
`75.
`
`76.
`77.
`78.
`79.
`80.
`
`81.
`82.
`
`83.
`84.
`85.
`
`86.
`87.
`88.
`89.
`90.
`91.
`
`92.
`93.
`94.
`95.
`96.
`97.
`
`1) {
`
`perror(“send”);
`exit(1);
`
`98.
`99.
`100.
`}
`101.
`bytes_sent += sizeof(payload);
`102.
`printf(“.”);
`103.
`fflush(stdout);
`Listing 3: (Continued)Code to limit the rate of
`transmission over a socket.
`
`Proceedings of the 18th Annual Computer Security Applications Conference (ACSAC(cid:146)02)
`1063-9527/02 $17.00 ' 2002 IEEE
`
`000007
`
`
`
`104.
`105.
`106.
`
`}
`elapsed = time(0) - begin;
`rate = bytes_sent / elapsed;
`
`printf(“\nRate = %8.3f bytes per
`107.
`second.\n”,rate);
`
`close(sd);
`
`108.
`109.}
`Listing 3: (Continued)Code to limit the rate of
`transmission over a socket.
`
`To implement this policy, SYS_so_socket and
`SYS_send system calls must be monito