Class 27 CS 372H 28 April 2011 On the board ------------ 1. Where are we? 2. Stack smashing ------------------------------------------------------------------------- 1. Where are we? --ended with a class on distributed systems. --top-level message: if you need to coordinate transactions across machines, use 2 phase commit. --make sure you understand why the failure of the coordinator leads to a blocking system --if you cannot tolerate a system that blocks if the coordinator fails, then use 3 phase commit. Don't hack it or make it up! --two guest lectures --copyright law and copy protection --binary rewriting --rest of the semester: security and protection 2. Stack smashing --history --('buffer overflow' is one way to conduct a stack smashing attack.) --demo [NOTE: fork/exec separation is what allows us to write tcpserve: after the fork() but before exec() of buggy-server, child rearranges its file descriptors to be the socket itself. Also, this sample code gives you a chance to see sockets in action.] --tig runs server. as Victor. --my laptop runs honest client --my laptop runs dishonest client --note: if this server had been running as root, we'd have been able to get a root shell --and if the user/syscall interface doesn't check its arguments properly, can buffer overflow that interface --in practice, once you have a user account on a machine, it's often possible to get root access (why? because the syscall interface is really hard to secure, as a matter of practice.) --other versions of these attacks --return-to-libc (see Tanenbaum) [DRAW PICTURE] --return-oriented programming [DRAW PICTURE] --overwriting function pointers --smashing the heap --how do people defend against these things? --map stack non-executable. but return-from-libc still works. --map stack and data non-executable (that is the W^X policy, roughly). but return-oriented programming still works. Also, W ^ X brings some issues: --the original 386 did not have a way to express W^X with page tables. However, all x86 chips that support extended page tables (which are used to help users get at >4GB of physical memory even if the machine is 32 bits) also support an XD bit in those page tables, which means "don't execute code in this page". We haven't worked with this bit in this class, but the architecture on modern 32-bit x86 supports it. --Even on x86s that don't suport extended page tables, segmentation would help with do-not-execute (since the permissions in the segment descriptor can express this). The disadvantage here is that the compiler needs to lay out the code and stack to match what the segments would require. --The bummer with W ^ X, even when it *is* supported, is this: some languages not only don't need it but also are actively harmed by W ^ X. The core of the issue is that a program written in a safe language (Perl, Python, Java, etc.) does not need W ^ X whereas lots of C programs do. Meanwhile some machines *always* enforce W ^ X, even for programs that do not need it. Such enforcement constrains certain languages, namely those that need to do runtime code generation. This is related to the topic of binary translation (yesterday's lecture) --Address space randomization. This provides some help but obviously doesn't help our vulnerable server because our server tells the client where the buffer is. --StackGuard (in gcc). --another defense: don't use C! CPUs are so fast that a language with bounds checking probably isn't going to pay a huge performance penalty relative to one without bounds checks --unfortunately, this is an arms race, and each time a new defense arises, a new attack arises too. here's the most advanced current technique, and it defeats many of the above defenses: --smash the stack with a bunch of return addresses. each return address points to the needed instruction followed by "ret" (requires the attacker to have previously identified these instructions in the code). not too hard in CISC code like on x86, where there are lots of sequences of code embedded in the binary, even sequences that the programmer didn't mean (because instructions are not fixed length). result: the control flow bounces around all of these byte sequences in memory, executing exactly what the attacker wanted, but not executing off of the stack. --this is called "return-oriented programming". defending against it is hard (though if people use only safe languages, that is, languages that do bounds checking and other pointer checks, such attacks will be much, much harder) --Question: can we instead confine processes and users so that when they're broken into, the damage is limited? --------------------------------------------------------------------------- Admin notes --sign up for project demo. use link we sent in email. --start your projects now. get some early deliverables out of the way.