In a word, no.
Buffer overflows can occur on both the stack and the heap. Stack-based ones are the classic target, since the saved stack pointer and return pointer are part of the stack frame and if you can overwrite either you can usually control the instruction pointer (IP) as soon as the function returns. However, there are ways to turn almost many kinds of memory corruption into arbitrary code execution.
A partial list of ways that are specific to buffer overflows:
- Tamper with heap memory allocation (i.e.
malloc) metadata, which is stored on the heap itself and usually near the allocated buffer. When the buffer is
freed, a value might incorrectly be written to a user-controlled address (such as the return pointer, or a function pointer variable, or...). This one is heap-specific.
- Overflow into a neighboring data structure that the attacker shouldn't be able to control, and change it in a way that gives code execution (such as by changing a function pointer that is later called, or changing an object's class reference so that it treats attacker-controlled data as its vtable, or changing the value of a pointer that is about to be written to). This can be done on both the stack and the heap.
- Change a neighboring variable so that code flows down a different path than it should (e.g. bypassing an authorization check and granting access when it shouldn't). You don't always need complete control over the IP to get the desired result; sometimes just getting the program to take the wrong branch is good enough!
A partial list of non-buffer-overflow-based ways to control the IP:
- Trigger a double-free, by passing data that causes the program to try calling
delete or whatever) on the same non-NULL address more than once (without re-allocating in between). This corrupts the heap metadata in predictable ways and can be used similarly to the first bullet above.
- Trigger a use-after-free, by providing data that results in the program taking a code path where it frees a buffer, allocates a new object in its place, but also continues using the original pointer (to the old object type). Treating a pointer to an object of one type as though it's a different type is often exploitable, for example by changing a field that is a harmless, user-controllable
int, but has the same address (in the other type) as a non-user-controllable function pointer.
- Format string vulnerability, where a function of the
scanf family is used but the format string is at least partly under attacker control, because the attacker can use the
%n format specifier to treat an arbitrary "word" along the stack as a pointer and write an attacker-controlled value to the address it "points to".
Memory corruption in managed-language environments (such as .NET, Java, Python, JS, etc.) is supposed to never happen (unless the managed code calls some native code) - that is, it's not supposed to be possible to write this class of bug in those environments - but managed-code runtimes are themselves written in native code, and native code sometimes has memory corruption bugs. The existence of such memory corruption bugs in those managed runtimes is often a serious security vulnerability, because a lot of them are used for running code from untrusted sources. For example, there have been many use-after-free bugs found in various browsers' JS runtimes, which can be exploited by a maliciously-crafted script to break out of the browser's JS sandbox and run code with the browser's privileges (this is a large part of why browsers are now sandboxed by the OS).