6 ms·
The pointer is read by the allocator, which then performs its own set of writes and management. I think you guys might be confusing stack memory and dynamic mem
by nyc_pizzadev 5y ago
The pointer is read by the allocator, which then performs its own set of writes and management. I think you guys might be confusing stack memory and dynamic memory here.
Interestingly enough, if you were to incorrectly use stack memory in this scenario, the magic checks should trigger, pointing out improper memory usage.
- DSMan195276 5y agoAllocation and free functions from the standard library are 'special' though and do actually get their own logic in regards to whether a pointer is necessary or not. If you do some simple tests you can see that gcc will optimize away entire `malloc()` and `free()` calls if it can prove the resulting memory or assignments are unnecessary (Ex. Store a constant to it, and then read the constant). It's willing to do that because the behavior of those functions is defined by the standard, so it 'knows' that removing those calls has no affect on the behavior of the program. I'm pretty sure you can get similar optimization behavior when you mark functions with special attributes, I'm not 100% sure on that point though. So for the Linux Kernel I'm not sure that kind of optimization would ever be done since obviously it's not using the standard defined functions and the compiler might not be given enough information to know the functions have the same semantics as 'malloc()' and 'free()'. I personally was able to get the exact behavior described here by compiling the below code using `-O2`. The volatile write just ensures the first constant write and the malloc itself cannot be optimized out (and that does happen! Take out the volatile and there are no calls to malloc in the result), but gcc is still free to do whatever it wants with the other write and for me it's completely gone in the resulting program. int main() { int *p = malloc(sizeof(*p)); *(volatile int *)p = 20; printf("p=%d\n", *p); *p = 30; /* this is gone from the -O2 compiled code */ free(p); return 0; } The relevant part of the assembly looks like this, I don't think I'm missing anything in that the 30 assignment is completely gone: push $0x4 call 460 <malloc@plt> movl $0x14,(%eax) # Assignment of 20 mov %eax,%esi pop %eax lea -0x1930(%ebx),%eax pop %edx pushl (%esi) # push the 20 push %eax call 440 <printf@plt> mov %esi,(%esp) # load address to pass to free(), no assignment between printf() and free() call 450 <free@plt>
- pjmlp 5y agoYes, it is gone. You can play with it on Compiler Explorer. https://godbolt.org/z/Ea3WPcad1 https://godbolt.org/z/Ea3WPcad1
- nyc_pizzadev 5y agoThe point of the magic values are that these values are eventually checked. This should break out of these kinds of optimizations. Edit note: allocators are user space libraries (stdlib) and not part of the C spec. Use-after-free is extremely unsafe, however its completely valid C. 2nd note: running all programs below give me the expected result, writes to pointers are live, regardless if they are freed. So please provide more concrete steps to reproduce your results.
- geofft 5y agoNo, it should not, because it is impossible for the check to fail in valid C. A use-after-free is invalid C. Type confusion is invalid C. If you have the function void free_ws(struct ws *ptr) { ptr->magic = 0xdeadc0de; free(ptr); } there is no possible circumstance, in valid C, where ptr->magic could be read and have its value equal to 0xdeadc0de. That object is freed immediately after that write. If you do a read in some other function, say char read_from_ws(struct ws *ptr) { if (ptr->magic != WS_MAGIC) { exit(1); } return ptr->s[0]; } it is impossible for the pointer passed into read_from_ws to be a pointer that has passed through free_ws, i.e., it is impossible for ptr->magic to have been set to 0xdeadc0de by free_ws. Therefore, free_ws doesn't need to actually do the write. You're right that the correct magic check is not dead, and cannot be eliminated by the same logic. But the effect there is that the magic check always succeeds!
- vlovich123 5y agoIf you compile with LTO the compiler will see through your obfuscation. Play dangerous games assuming the compiler is stupid, win dangerous prizes.
- DSMan195276 5y ago