It seems from some of the comments down the thread that the idea of writing over memory in C++ is new to many people. Let me introduce another couple of concepts.
You don't have to stop with writing over the variables. You can write over the return address of a function, which is stored on the same stack as the variables. If you write some user-supplied buffer to memory, and you didn't carefully make sure that the memory is allocated for this purpose, the (malicious) user can supply you with data that will set the function's return address to something in his buffer, and the function will go on executing his code. That's called a buffer overflow vulnerability, and it's been well known and exploited for decades; half the patches you see coming down the pipeline used to be for this.
Now, there are good ways to protect against buffer overflow vulnerabilities. If you make the stack and data non-executable, and the pages containing code non-modifiable, then seemingly there is no place for the attacker to place his code and get it to run. Modern processors support this in hardware.
Except that some people came up with something called return-oriented programming. This works by finding little pieces in your code that do something simple like increment a register or write a byte of data, followed by a "return" instruction. These tiny pieces of code are called "gadgets". You can usually find enough gadgets to make a Turing-complete language. Then you write a compiler which transforms your code into a stream of calls to the gadgets. Now all you need is one overwrite of the return address on the stack, and you can start playing the application like a piano, without ever executing data or modifying code. There are now tools to automate all this.
I don't want to reveal anything about where I work, but we write applications which need to be reliable and secure. We have regular meetings to discuss vulnerabilities and security techniques. At the meeting where we discussed return-oriented programming, when we asked what we could do to protect from it, the consensus answer was "You can't. Don't make any buffer overruns." The problem is it's not clear that anyone ever wrote a program free from those.
One last addition - I hope I'm not overstaying my welcome with all these little lectures on security. It used to be that a buffer overrun typically resulted from carelessness, like
char name[100]; //Who has a name longer than 100 characters?
printf(“What is your name?”);
scanf(name, ”%s”);
It turns out that the same kind of people who have names like "Robert'); DROP TABLE Students; --" sometimes have 150-character names with a return address and a few bytes of malicious code. That's something that you handle kind of like you sanitize your inputs. You don't pour user input into a limited space. Scanf is deprecated now, and this kind of a vuln is becoming rare.
But as I explain a couple of comments down, a buffer overflow doesn't have to be this simple. You can get a bad pointer in a structure anywhere in your program, and when you use the structure, you will overwrite the stack. It's very hard to catch that kind of vuln - basically, it's a race with you (and white hats) vs. black hats. This is why I welcome the switch from C/C++ to managed code which is happening now, but very, very slowly.
Off topic/meta: In the days when comment scores used to be shown, I could do the HN-equivalent of saying "hear, hear!" [or as the Internet says it: "here, here" :)] by upvoting a "thank you" note like the above. Now, if I really want to let you know that I too liked your comment, I have to post a "me too!" comment like this one. Anyway, thanks. TIL about "return oriented programming".
Thanks, I'll edit that, since this seems to be a popular thread. I guess I just provided the visual proof of how people make mistakes even when they should know better.
Your post is indeed very useful, but for the sake of completeness I'll add some remarks to it.
The technique you call Return Oriented Programming is not as new as you seem to beleive. It evolved from ideas and techniques that have been arround since late 90s, mainly what is called "return to lib". Surely it was not as popular and hyped as it is today, but then again neither was a non-executable stack popular back then.
Also an effective implementation of Address Space Layout Randomization serves as a mitigation to ROP shellcode. I'm no expert on exploit writing but I beleive that modern ASLR on 64 bits system will be very difficult to bypass except for the most skilled exploit writers out there, and they would most surely need to pinpoint exact versions of your OS, libraries and layout of your binary.
I don't know any specifics about your work, but though "You can't. Don't make any buffer overruns" is a very good advice, its rather simple-minded... I firmly believe any bug can be leveraged into code execution until someone shows the contrary for said bug, but that doesn't mean that a single technique is the silver bullet of exploit writing. To be able to code in a security-minded way, as one learns about were each protection might fail, one must also learn about how each exploit writing technique might fail.
A good introduction to ROP in the form of a somewhat self oriented self contained blogpost can be found here:http://eticanicomana.blogspot.com/2010/06/so-called-return-o... by a guy from Immunity Inc. I'm mentioning his post because I think one of the automated gadget finding tools you may be talking about is their Immunity Debugger/DEPLib.
I agree with you, and I didn't mean to imply that ROP is all that new. It's been on my personal radar since 2008, which is when it became really popular. I think this was also when the automated tools for exploits started to appear. Of course, the basic techniques go back before then. This happens with computer science - we had to give up writing a certain patent [not security-connected] because we found the basic idea in a paper from 1956.
We discussed address space randomization, which of course we use, and our conclusion was that for us, this doesn't provide enough protection. Not every location can be randomized with equal effectiveness, and sometimes just having an offset is enough, and we're a high-value target. Thus, the advice to "not make buffer overruns." I am also not an exploit writer, or even specifically a security guy (although I need to be aware of it). Obviously, return-oriented programming is not a "we're all going to die!!!" thing. But it's an extremely dangerous technique, which really illustrates the danger of writing user-facing code in a language with no memory safety.
> The problem is it's not clear that anyone ever wrote a program free from [buffer overruns].
There are lots of programs in languages like COBOL that don't have buffer overruns, because they don't have pointer arithmetic.
Nobody has ever found a buffer overrun in qmail, despite looking very hard for many years.
It is very likely that my toy compiler http://canonical.org/~kragen/sw/urscheme/ doesn't have any buffer overruns. It's written in Scheme, generating x86 assembly. The only places that do pointer arithmetic under the control of user code are string-ref and string-set!, which call the same check-array-bounds routine to verify that the index is within the bounds of the string. Unless I fucked up that check, which is seven lines of assembly, there's no way to get a buffer overrun in code compiled with the compiler.
(Technically there's also code having to do with closures that could potentially have a buffer-overrun bug, but it's much less likely. Indeed, virtually any of the assembly language output of the compiler could conceivably have a bug that smashes memory. But it probably doesn't.)
There's also no way to efficiently move data in and out of a string, because every access is individually bounds-checked. But reasonably-efficiently-bounds-checked array access, while it's more than seven lines of assembly, isn't an unboundedly-large problem either. It's just that there's always a tradeoff between safety and efficiency.
I'm obviously not talking about programs written in memory-safe languages - my point is that they don't have buffer overruns.
About the programs written in unsafe languages, I think it's clear that I don't literally mean that every single one has a buffer overrun; you can ensure that simple programs don't, and I am also assuming that well-designed language runtimes and compilers do not. Otherwise, you'd gain nothing by going to a memory-safe language. In the real world, something like a JIT compiler or a JVM is far less likely to be unsafe than an average app, although it does have bugs and there were a couple of patches for buffer overruns.
Also, programs which essentially transform strings to strings, like compilers, are easier to make formally correct, or at least correct enough that you have confidence in them, than many other classes of programs. If you have to deal with timings, polling, complicated interactions with the environment - it all becomes much harder. Also, compilers can have tight specifications of what they do - I don't think it's possible to specify the behavior of a word processor to the same extent. For many C/C++ apps, it's just hard to ever say they don't have a buffer overrun.
So you can imagine our feeling when learning about the ROP attacks. They're going to write a compiler for their malicious code which targets a virtual machine made up of tiny pieces of our app. What kind of a world is this? And they only need one buffer overrun. A scary feeling, although, as someone already pointed out, the idea of running a little piece of code from somewhere is not new, and the attack is not the end of the world.
> I am also assuming that well-designed language runtimes and compilers do not.
I guess the big point I was trying to make in other parts of the thread is that this is sort of begging the question. If you can assume that runtimes and compilers are flawless, then of course you should use the biggest, hairiest ones you can get your hands on, and furthermore you should argue for every possible piece of functionality to be put into the runtime, so that it will work correctly and not have any bugs.
In practice, though, that is not the best possible factoring of the problem. If it were, you wouldn't be using C++! So I don't think it provides a very good guideline to use for questions like "would moving from C++ to Java make our code more reliable?" I think that answering that question properly involves a lot more reasoning about the particular runtimes and compilers involved, as well as the structure of your application — or lack thereof, if you really have an unbounded number of places you might have written a buffer overflow!
What you say about compilers' formal correctness might be true in theory, but in practice, all compilers contain a large number of bugs. I don't think it is true in theory, though. Optimizing a piece of code is AI-complete, and verifying the equivalence of the source and target programs is known to be uncomputable, since it subsumes the halting problem — in theory, it's impossible to even determine whether the compiler has inserted an infinite loop into your program, let alone whether the rest of its semantics are preserved.
The string → string nature of compilers does mean that they can be highly portable and not spend much time interfacing with the rest of the universe, though.
> I don't think it's possible to specify the behavior of a word processor to the same extent.
Maybe not, but you can probably specify the behavior of its string class to have no buffer overruns. I mean, you can write a word processor in Python, right?
You don't have to stop with writing over the variables. You can write over the return address of a function, which is stored on the same stack as the variables. If you write some user-supplied buffer to memory, and you didn't carefully make sure that the memory is allocated for this purpose, the (malicious) user can supply you with data that will set the function's return address to something in his buffer, and the function will go on executing his code. That's called a buffer overflow vulnerability, and it's been well known and exploited for decades; half the patches you see coming down the pipeline used to be for this.
Now, there are good ways to protect against buffer overflow vulnerabilities. If you make the stack and data non-executable, and the pages containing code non-modifiable, then seemingly there is no place for the attacker to place his code and get it to run. Modern processors support this in hardware.
Except that some people came up with something called return-oriented programming. This works by finding little pieces in your code that do something simple like increment a register or write a byte of data, followed by a "return" instruction. These tiny pieces of code are called "gadgets". You can usually find enough gadgets to make a Turing-complete language. Then you write a compiler which transforms your code into a stream of calls to the gadgets. Now all you need is one overwrite of the return address on the stack, and you can start playing the application like a piano, without ever executing data or modifying code. There are now tools to automate all this.
I don't want to reveal anything about where I work, but we write applications which need to be reliable and secure. We have regular meetings to discuss vulnerabilities and security techniques. At the meeting where we discussed return-oriented programming, when we asked what we could do to protect from it, the consensus answer was "You can't. Don't make any buffer overruns." The problem is it's not clear that anyone ever wrote a program free from those.
Since hotel analogies seem to be so popular, here you go: http://en.wikipedia.org/wiki/Psycho_(film)