I was recently trying out a stack overflow exercise on x64. When performing this on x86, I would expect the following for a junk overwrite address (e.g. 'AAAA'):
- The data I provide overflows the buffer, and overwrites the return address
- Upon
ret
, the (overwritten) return address will be (effectively) popped into the EIP register - It is realised that the address is not valid, and a segmentation fault is raised
In x64, this seems different (beyond the interchange of EIP with RIP in the above steps). When providing a junk address of 'AAAAAAA', the processor seems to do some validity checking before popping the address. By observation, it seems required that the two most significant bytes of the address are null, before it is loaded. Otherwise, a segfault occurs. I believe this is due to the use of 48-bit addressing in x64, however I was under the impression that addresses starting with 0xFFFF were also valid, yet this also produces a segfault.
Is this an accurate description of the difference? Why is this check performed before the data is loaded into the RIP register, whilst the other validity check is performed afterwards? Are there any other differences between these instructions?
EDIT: To clarify my observations, I note that when a 8-byte return address is provided, the RIP still points to the address of the ret
instruction, and the RSP still points to the overwritten return address on segfault. When an 6-byte return address is provided, the overwritten address has been popped into the RIP when the segfault is observed.
rip
register might not even have the bits in hardware so it can't be loaded. Yes, the top part of the address range should also be canonical, but you must use sign extension. So for example0xffff800000000000
is canonical and will be loaded intorip
and only fault afterwards :)0xffff4141414141414141
is not canonical. – Jester0xffff
are only valid in kernel mode afaik. – fuzffffffffff600000-ffffffffff601000
range into user-space processes as the[vsyscall]
page. (cat /proc/self/maps
). – Peter Cordesret
is complicated by clutter fromretf
(far), but IA-32E-MODE-RETURN-TO-SAME-PRIVILEGE-LEVEL: includesIF the return instruction pointer is not within canonical address space THEN #GP(0); FI;
. Instead of#PF
, but GP or invalid pagefault both get the kernel to deliver SIGSEGV – Peter Cordesret
instruction's attempt to set RIP to a non-canonical address. That makes the whole RET instruction fault, meaning that none of its effects are visible. – Peter Cordes