Also, it seems weird to me that the heap pages are marked as executable by default.
Also, it seems weird to me that the heap pages are marked as executable by default.
1: Yes, you will eventually need to mmap more memory. Since you want to minimize the number of mmap calls necessary for things like heap allocations, you generally allocate in larger chunks then necessary and then malloc etc. allocates from these chunks. It doesn't matter too much in terms of memory waste, since in the back end, linux allocates physical memory lazily, so the extra allocated block of virtual memory doesn't get a physical allocation until something tries to access it. For this reason, it can sometimes seem like a malloc call completes successfully, but you run into an out of memory condition once you try to actually use your "successfully" allocated memory.
2. It turns out most kernels don't actually enable the NX bit automatically, with the reason given being that early processors have buggy implementations, so by default the NX flag is not honored. As a result, all memory is executable by default, unless explicitly set not to be (i.e. using memprotect), and this is only if the kernel is compiled to support NX, which it may not be. This is changing, and some systems such implementing SELinux or similar are supposedly stricter but I haven't actually tested this out myself.
2. If only this were true, I regularly pine for the days of EIP -> 0xc0c0c0c0. It is true that some compilers generate entirely RWE memory pages, but usually for much different reasons (Executable stacks for GCC trampolines, for example)
malloc could be used to expand the heap, which conveniently appears after .bss. The pointer returned would probably still need to be followed, since heap allocations might not be contiguous (and mprotect needs to be used to mark the pages executable).