There is no reason why the C/C++ stack can't grow up rather than down. On paged hardware, both the stack and heap could (and probably should) grow up. "C's stack should grow up", one might say.
Historical accident. Imagine if PDP-7/PDP-11 easily allowed for the following memory layout:
FFFF +---------------+
| text | X
+---------------+
| rodata | R
+---------------+
| data + bss | RW
+---------------+
| heap |
| || | RW
| \/ |
+---------------+
| empty space | unmapped
+---------------+
| /\ |
| || | RW
| stack |
0000 +---------------+
Things could have turned out very differently than they have. Oh well.ARM does not suffer from that problem due to the usage of link registers and generic pre/post-modify. RISC-V is probably also safe, but I have not looked specifically.
I wonder what the best way to do it (on current x86) would be. The stupid simple way might be to adjust SP before the call instruction, and that seems to me like something that would be relatively efficient (simple addition instruction, issued very early).
Instead of
addi sp, sp, -16
sd a0, 0(sp)
sd a1, 8(sp)
Do: addi sp, sp, 16
sd a0, -8(sp)
sd a1, -16(sp)One source from a few mins of searching: https://phrack.org/issues/58/11
As noted, stack growing up doesn't prevent all stack overflows, but it makes it less trivially easy to overwrite a return address. Bounded strings also made it less trivially easy to create string buffer overflows.
Most exploitable memory corruption bugs are heap buffer overflows.