A modification to %esp cause SIGSEGV
It's because in x86-64 assembly modifying a 32-bit register such as esp
zeroes the highest 32 bits of the corresponding 64-bit register, rsp
in this case. In x86-64 the stack pointer rsp
is a 64-bit register.
x86-64 Linux always puts the stack pointer near the top of the user-space range of virtual address space, like 0x7fffffffe6d0
for example, so it's always outside the low 32 bits of virtual address space. The high half of RSP is non-zero and ESP != RSP.
So by modifying esp
you make rsp
point to an address where your program has no access rights, and thus cause a segmentation fault. In x86-64 code you don't normally use esp
at all, and you should replace all instances of esp
with rsp
in your x86-64 code.
Segfault when loading from [esp] in 64-bit code
I'm in x64 mode, and like a bunch of people suggested in the comments using mov ebx, [rsp+8]
worked, because esp
are just the 4 lower bytes of the register. The stack is outside the low 4 GiB of virtual address space, so ESP != RSP and [esp]
will be an unmapped page.
Note that x86-64 calling conventions pass the first few args in register, not on the stack, so you normally don't want to do this at all (unless your function has lots of args).
ESP register dereference segmentation fault, x86
In 64-bit mode, the stack pointer is RSP, not ESP. Using ESP uses the low 32 bits of the register, which isn’t a valid memory address.
Reason for SIGSEGV
But, the destination address of this instruction is a valid address and this memory is mapped as writable.
Not it's not (or the instruction wouldn't have caused a SIGSEGV
).
The destination 0x80f8f5c
is "covered" by this LOAD segment:
LOAD 0x0b06dc 0x080f86dc 0x080f86dc 0x015f8 0x07730 RW 0x1000
but also by this:
GNU_RELRO 0x0b06dc 0x080f86dc 0x080f86dc 0x00924 0x00924 R 0x1
the GNU_RELRO
asks the runtime loader to make this part of address space read-only after the loader has performed the relocation (which is exactly what it did, and what triggered your crash).
Is there a way in gdb to figure out if the address is mapped as readonly or not?
You can ask gdb with info proc map
, or just look in /proc/<pid>/maps
. Either way you'll discover that the memory is mapped read-only.
what is causing SIGSEV?
long size = 10000000;
struct foo *bar[size];
will create a very big array, which may cause stack overflow, and therefore your program receive the SIGSEV.
You should create this array dynamically:
struct foo *bar = malloc(size * sizeof(struct foo *));
Why does the program work normally if these is not any function call in main()
?
The definition of foo
will cause main()
to have a large stack frame at runtime. If you does not call any function in main()
, this large stack frame will not be actually allocated or accessed (the entrance code of main()
only make sure that amounts of memory be reserved by manipulating some registers and memory cells); but if you call a function in main()
, the calling itself will try to access some addresses in that main()
stack frame, because of stack overflow, those addresses may not be valid, this will cause SIGSEV be sent.
If you disassemble and compare the working and not-working versions of this program, this would be obvious. You could also find it out by stepping through the instructions of not-working main()
one by one.
Without function call in main()
:
0x00001ff0 <main+0>: push %ebp
0x00001ff1 <main+1>: mov %esp,%eax
0x00001ff3 <main+3>: mov %esp,%ebp
0x00001ff5 <main+5>: sub $0x2625a10,%esp
0x00001ffb <main+11>: mov %eax,%esp
0x00001ffd <main+13>: leave
0x00001ffe <main+14>: ret
Call exit()
in main()
:
0x00001fe0 <main+0>: push %ebp
0x00001fe1 <main+1>: mov %esp,%ebp
0x00001fe3 <main+3>: sub $0x2625a28,%esp
0x00001fe9 <main+9>: movl $0x0,(%esp) <==== This causes segfault.
0x00001ff0 <main+16>: call 0x3000 <dyld_stub_exit>
Why segmentation fault doesn't occur with smaller stack boundary?
You're not overwriting the saved eip, it's true. But you are overwriting a pointer that the function is using to find the saved eip. You can actually see this in your i f
output; look at "Previous frame's sp" and notice how the two low bytes are 00 35
; ASCII 0x35 is 5
and 00
is the terminating null. So although the saved eip is perfectly intact, the machine is fetching its return address from somewhere else, thus the crash.
In more detail:
GCC apparently doesn't trust the startup code to align the stack to 16 bytes, so it takes matters into its own hands (and $0xfffffff0,%esp
). But it needs to keep track of the previous stack pointer value, so that it can find its parameters and the return address when needed. This is the lea 0x4(%esp),%ecx
, which loads ecx with the address of the dword just above the saved eip on the stack. gdb calls this address "Previous frame's sp", I guess because it was the value of the stack pointer immediately before the caller executed its call main
instruction. I will call it P for short.
After aligning the stack, the compiler pushes -0x4(%ecx)
which is the argv
parameter from the stack, for easy access since it's going to need it later. Then it sets up its stack frame with push %ebp; mov %esp, %ebp
. We can keep track of all addresses relative to %ebp
from now on, in the way compilers usually do when not optimizing.
The push %ecx
a couple lines down stores the address P on the stack at offset -0x8(%ebp)
. The sub $0x20, %esp
makes 32 more bytes of space on the stack (ending at -0x28(%ebp)
), but the question is, where in that space does buffer
end up being placed? We see it happen after the call to dumb_function
, with lea -0x20(%ebp), %eax; push %eax
; this is the first argument to strcpy
being pushed, which is buffer
, so indeed buffer
is at -0x20(%ebp)
, not at -0x28
as you might have guessed. So when you write 24 (=0x18
) bytes there, you overwrite two bytes at -0x8(%ebp)
which is our stored P pointer.
It's all downhill from here. The corrupted value of P (call it Px) is popped into ecx, and just before the return, we do lea -0x4(%ecx), %esp
. Now %esp
is garbage and points somewhere bad, so the following ret
is sure to lead to trouble. Maybe Px
points to unmapped memory and just attempting to fetch the return address from there causes the fault. Maybe it points to readable memory, but the address fetched from that location does not point to executable memory, so the control transfer faults. Maybe the latter does point to executable memory, but the instructions located there are not the ones we want to be executing.
If you take out the call to dumb_function()
, the stack layout changes slightly. It's no longer necessary to push ebx around the call to dumb_function()
, so the P pointer from ecx now winds up at -4(%ebp)
, there are 4 bytes of unused space (to maintain alignment), and then buffer
is at -0x20(%ebp)
. So your two-byte overrun goes into space that's not used at all, hence no crash.
And here is the generated assembly with -mpreferred-stack-boundary=2
. Now there is no need to re-align the stack, because the compiler does trust the startup code to align the stack to at least 4 bytes (it would be unthinkable for this not to be the case). The stack layout is simpler: push ebp, and subtract 24 more bytes for buffer
. Thus your overrun overwrites two bytes of the saved ebp. This is eventually popped from the stack back into ebp, and so main
returns to its caller with a value in ebp that is
not the same as on entry. That's naughty, but it so happens that the system startup code doesn't use the value in ebp for anything (indeed in my tests it is set to 0 on entry to main, likely to mark the top of the stack for backtraces), and so nothing bad happens afterwards.
x86_64 assembly %rsp vs %esp
In x86_64, addresses are 64 bits, so how can you expect to do 32-bit math on it and still working fine? Moreover most 32-bit operations on x86_64 zero out the top 32 bits, render the address invalid
Related Topics
How to Cut an Existing Variable and Assign to a New Variable in Bash
Linux: Modpost Does Not Build Anything
Why Is Gdb Breakpoint Set at the Wrong Address for an X86 Assembly Function
Unable to Pass Wget a Variable with Quotes Inside the Variable
Magic Numbers of the Linux Reboot() System Call
How to Get Perf to Find Symbols in My Program
List File Using Ls Command in Linux with Full Path
How to Automate HTML-To-Pdf Conversions
Can't Remove a Directory in Unix
Get Filesystem Mount Point in Kernel Module
Best Posix Way to Determine If a Filesystem Is Mounted Read Only
Merge Files with Bash by Primary Key
Docker Networking Namespace Not Visible in Ip Netns List
How to Delete First Two Lines and Last Four Lines from a Text File with Bash