Riyaz Faizullabhoy's answers ----------------------------- Beyond Stack Smashing: Recent Advances in Exploiting Buffer Overruns In this article, the authors summarize classes of exploits that derive from buffer overruns - when a program attempts to read or write beyond the specified length of a bounded array, most usually occurring in C/C++ due to lack of bounds checking. While the article details a list of attacks including stack smashing, arc injection, pointer subterfuge, and heap smashing, the article also abstracts buffer overflow exploits into a two-step process of changing the program's control flow in order to then execute code on data in some malicious manner. The executed code and data being operated on can be either pre-existing or supplied by the attacker, as long as the attacker is aware of the buffer overrun vulnerability and that the code or data (or both) to be used in the attack is at a known or discoverable memory location. ' After abstracting the root cause and flow of buffer overrun vulnerabilities, the article describes each class of attack in more detail, starting with stack smashing. Stack smashing is a technique that takes advantage of how C compilers store local variables and return addresses on the same stack — by overrunning a local variable buffer at a suitable memory location, an attacker can overwrite the return address and also inject malicious code to be executed. Heap smashing follows a similar flow, but instead overruns memory on the heap, which is more challenging to locate and ensure validity of. Instead of injecting code onto the stack or heap directly, the arc injection technique takes advantage of pre-existing code (such as library code) and executes it with attacker-supplied data, resistant to nonexecutable stacks. Pointer subterfuge techniques overwrite function-pointers, data-pointers, exception-handler function pointers, and virtual pointers modify existing pointers to point to attacker-code or modify data (in the case of data-pointer smashing) to work around possible mitigations and set up further attacks. Baggy Bounds Checking: An Efficient and Backwards-Compatible Defense against Out-of-Bounds Errors In this paper, the authors present a backward-compatible bounds checking technique to prevent out-of-bounds errors that can be exploited. This technique is deemed "baggy bounds checking," and enforces "allocation bounds" which leave room for out-of-bound accesses past the object that are benign, but not past its precise allocation. The allocation bounds are chosen in powers of two, which has nice properties for relating to the binary address pointer of an allocation (by clearing the lower significant bits to get the base). The baggy bounds checking system follows a similar architecture of other bounds checking systems by converting source code to an intermediate representation to insert its bounds checks before generating code and linking to its libraries. The system also uses the referent object approach for bounds checking, maintaining a bounds table that is updated on allocation/deallocation of objects -- the powers of 2 allocation sizes make this representation in the bounds table very efficient, allowing bounds information to fit in a single byte. This small size allows baggy bounds to implement its bounds table using a contiguous array, which has faster lookups than the canonical splay tree for other bounds checking systems. The system also uses static analysis techniques to identify "unsafe" local variables that require bounds checking, to save space and complexity. In its implementation, baggy bounds checking has special operations to allow for padding unsafe argument variables (by copying to other variables), and implements optimizations to hoist checks out of loops in certain cases. Performance-wise, baggy bounds checking's space array performs much quicker lookups than the traditional splay tree (without increasing space) and only decreases throughput of an Apache webserver by approximately 8%. Baggy bounds checking prevented 17 of 18 buffer overflows in a benchmark suite, only missing an overflow of an array inside a structure clobbering a pointer in the same struct -- which, by design, cannot be prevented by baggy bounds checking's memory block-level checking scheme. Grant Ho's answers ------------------ Baggy Bounds Checking Baggy bounds checking is a runtime defense that uses source code rewriting and a modified runtime library to prevent memory vulnerabilities that stem from out-of-bounds pointers; this allows programs to detect and mitigate memory safety violations such as buffer/array overflows. At a high level, the baggy bounds runtime library modifies the memory allocator to align the size of all allocated objects to powers of two: if an object (e.g. a buffer) takes up less space than an exact power of two, the object is padded until its size is aligned. With this modified memory allocator, baggy bounds checking can instrument the program's source code with fast runtime checks that verify every pointer dereference remains within its "allocation bounds". Namely, given a pointer to an object, baggy bounds checks ensure that all pointers derived from this object pointer still point to the original object. The power-of-two alignment enables this monitoring code to efficiently and safely perform the bounds checking with just one comparison operation and simple bit shifting. Additionally, this alignment allows fast indexing and efficient space conservation for the bounds table (although I'm a bit confused over how the table is updated/indexed and what the authors mean by slots based on the paper). Testing the performance their implementation, the authors find that baggy bounds checking adds an average of zero latency overhead to the Olden benchmark suite; however, against the SPECINT 2000 suite, baggy bounds checking adds 60% latency overhead and 15% space overhead (measured at Peak memory). The authors discuss ways to optimize baggy bounds checking in 64-bit architectures; by leveraging unused bits in pointer objects to store additional bounds information, baggy bounds checking can replace the bounds table (effectively making "fat pointers"). While baggy bounds checking prevented 17/18 buffer overflows in a buffer overflow benchmark suite, it suffers from the safe limitations as other bounds checking techniques: baggy bounds checking cannot protect unsafe typecasting to pointers, unchecked/uninstrumented code, dangling or double-free pointer vulnerabilities, and memory errors in sub-objects such as struct fields. Beyond Stack Smashing This paper discusses three advances in control flow hijacking attacks that enhance early stack smashing attacks. First, arc injection (ROP attacks) allow an adversary to hijack control via buffer overflows, without injecting malicious data into the program. Typically this technique is used to stitch together "gadgets" of existing library code (e.g. from libc) to allow an attacker to perform arbitrary code execution from already loaded code; this allows an adversary to bypass defenses like DEP. Second, the authors discuss pointer subterfuge attacks, which corrupt pointer values to achieve arbitrary memory writes or control hijacking. For example, function pointers can be overwritten to point to malicious code (or used to initiate a ROP attack); similarly, exception handlers and VTBL's can be overwritten for control flow hijacking; additionally, data pointers can be overwritten to modify the value of arbitrary blocks of memory (by changing the ptr's address to a target address and overwriting the assignment/pointee's value to an attacker-chosen value). Finally, several heap smashing attacks have been discovered to obtain control flow hijacking or unauthorized memory reads; e.g. exploiting double-free and dangling heap pointer vulnerabilities to corrupt header information used by heap allocators. Ultimately, ROP attacks show that exploits can be conducted without injecting any malicious code; pointer subterfuge attacks show that control flow hijacking can be obtained without overwriting any stack frame return addresses; and heap smashing attacks show that buffers and data on the heap are also vulnerable to memory safety attacks.