Even in following good coding practices, arbitrary code execution bugs can still exist. By leveraging pledge(2) system calls and a static analysis framework, we attempt to mitigate these bugs by automatically inserting pledge statements. Although an algorithm was devised to do this, time limitations prevented its full implementation.
System and high performance computing code are often written in low level languages such as C and C++. Low level languages provide high performance but are open to memory corruption attacks due to the lack memory safety and manual memory management. Memory corruption attacks (Szekeres 2013) manifests through memory errors such as overflows, underflows or dangling pointers. One main type of memory corruption attack is control-flow hijacking that takes control of the entire program by using memory errors to enable the write and execution of desired instructions. The classic control-flow attack that works by overwriting in memory programs with new desired instructions can prevented with non-executable data policies. However, attacks can use Return Oriented Programming (ROP) to bypass data policies by chaining together in-memory code. The library used for ROP attacks is libc, these attacks are referred "return-to-libc" attacks. Libc is ideal for attacks because it likely to be already residing in memory and there are enough instruction sequences in libc's functions that make it Turing complete by chaining together these sequences(Tran 2011). Currently there are no policies that prevent ROP because it executes valid code from memory, but mitigation techniques (Li 2010, Pappas 2012, Raadt) can be applied to reduce the effectiveness of such attacks.
Based on the observation that ROP uses many libraries that make system calls, De Raadt proposed a new mitigation technique against control-flow attacks Pledge(Raadt) for the OpenBSD operating system. To reduce attack surfaces, programs in OpenBSD can be annotated with pledge(pledge — restrict sys...) promises to limit the number of systems calls the program can make. Promises used in pledges are groupings of system calls that are related. For example, the stdio promise includes system calls required for IO such as fsync, read, write, pipe, etc. When running a pledged program, the kernel can enforce the pledged promises and report an error if the program attempts to make system calls that are not allowed.
To pledge a program, a list of all the system calls that the program can use is required. Over-pledged programs can malfunction while under-pledged programs can leave more attack surfaces than necessary. Therefore, a full understanding of the program's behavior and the interfaces it uses is required to correctly identify the list of all the system calls a program can use. Once all the system calls for a program is determined, they can be converted to promises for use in pledge.
Augmenting programs with pledges is non-trivial and time consuming even for simple UNIX utility programs. Pledging large programs is more challenging for developers due to difficulty in accurately determining behaviors of complex systems and the lack of understanding in low level libraries. Similar to permissioning in mobile operating systems and web browsers, these complications can lead to overpermissioning in real world applications (Felt 2011)(Jackson 2009). To solve this problem, we develop an automated system Autopledge to analyse C programs and insert pledges in optimal locations.
This paper make the following contributions: